Introduction


The aim of this analysis is to predict customer churn in a Telecom company. Two classification models will be used to predict the likelyhood of customer churn. The two models will be compared based on their accuracy.

Data Set


The data set consists of 7,043 observations of 21 variables as shown below:

Let’s look at the distinct values present in each categorical variable.

Some factor variables have 3 types, let’s see what those values are, and if we can group them in anyway.

options(width = 10)
telco_customer_churn_raw_data %>%
     summarise_all(funs(n_distinct(.)))
as.data.frame(table(telco_customer_churn_raw_data$MultipleLines))
as.data.frame(table(telco_customer_churn_raw_data$InternetService))
as.data.frame(table(telco_customer_churn_raw_data$OnlineSecurity))
as.data.frame(table(telco_customer_churn_raw_data$OnlineBackup))
as.data.frame(table(telco_customer_churn_raw_data$DeviceProtection))
as.data.frame(table(telco_customer_churn_raw_data$TechSupport))
as.data.frame(table(telco_customer_churn_raw_data$StreamingMovies))
as.data.frame(table(telco_customer_churn_raw_data$StreamingTV))

As we can see, some of the factors have 3 values, but the categories “No Internet Service”/“No Phone Service” are not adding any additional value as that information is already captured in a separate variable. So, let’s update these two values to “No”.

Also, since all categorical variables are in yes/no format except Senior Citizen, we can transform that also into Yes/No format. Let’s also transform the output variable Churn into 1/0 format so that we do not have to dummify it further.

#Transform MultipleLines Variable
telco_customer_churn_transformed_data <- telco_customer_churn_raw_data %>%
     mutate(MultipleLines = ifelse(MultipleLines=="No phone service","No",MultipleLines))
#Transform OnlineSecurity Variable
telco_customer_churn_transformed_data <- telco_customer_churn_transformed_data %>%
     mutate(OnlineSecurity = ifelse(OnlineSecurity=="No internet service","No",OnlineSecurity))
#Transform OnlilneBackup Variable
telco_customer_churn_transformed_data <- telco_customer_churn_transformed_data %>%
     mutate(OnlineBackup = ifelse(OnlineBackup=="No internet service","No",OnlineBackup))
#Transform DeviceProtection Variable
telco_customer_churn_transformed_data <- telco_customer_churn_transformed_data %>%
     mutate(DeviceProtection = ifelse(DeviceProtection=="No internet service","No",DeviceProtection))
#Transform TechSupport Variable
telco_customer_churn_transformed_data <- telco_customer_churn_transformed_data %>%
     mutate(TechSupport = ifelse(TechSupport=="No internet service","No",TechSupport))
#Transform Streaming Movies Variable
telco_customer_churn_transformed_data <- telco_customer_churn_transformed_data %>%
     mutate(StreamingMovies = ifelse(StreamingMovies=="No internet service","No",StreamingMovies))
#Transform Streaming TV Variable
telco_customer_churn_transformed_data <- telco_customer_churn_transformed_data %>%
     mutate(StreamingTV = ifelse(StreamingTV=="No internet service","No",StreamingTV))
#Transform Senior Citizen Variable
telco_customer_churn_transformed_data <- telco_customer_churn_transformed_data %>%
     mutate(SeniorCitizen = ifelse(SeniorCitizen==1,"Yes","No"))
#Transform Churn Variable
telco_customer_churn_transformed_data <- telco_customer_churn_transformed_data %>%
     mutate(Churn = ifelse(Churn=="Yes",1,0))

Let’s look at the structure of the dataset.

telco_customer_churn_transformed_data %>%
     plot_str()

From the above graph we can notice that some categorical variables are currently ‘characters’. We can convert them to factors for more accurate representation.

telco_customer_churn_transformed_data <- telco_customer_churn_transformed_data %>%
     mutate(SeniorCitizen = as.factor(SeniorCitizen),
            gender = as.factor(gender),
            Partner = as.factor(Partner),
            Dependents = as.factor(Dependents),
            SeniorCitizen = as.factor(SeniorCitizen),
            PhoneService = as.factor(PhoneService),
            MultipleLines = as.factor(MultipleLines),
            InternetService = as.factor(InternetService),
            OnlineSecurity = as.factor(OnlineSecurity),
            OnlineBackup = as.factor(OnlineBackup),
            DeviceProtection = as.factor(DeviceProtection),
            TechSupport = as.factor(TechSupport),
            StreamingTV = as.factor(StreamingTV),
            StreamingMovies = as.factor(StreamingMovies),
            PaperlessBilling = as.factor(PaperlessBilling),
            Contract = as.factor(Contract),
            PaymentMethod = as.factor(PaymentMethod),
            Churn = as.factor(Churn))

Data Exploration


As the graph below shows, 99.8% of the observations are complete.

telco_customer_churn_transformed_data %>%
     plot_intro()

The below graph shows us that “TotalCharges” has missing values. Though they are very few, we will check the reason behind this in further sections.

From the below count we can see that there are no duplicate Customer IDs in the dataset. There are 7043 records and 7043 unique Customer IDs

telco_customer_churn_transformed_data %>%
     summarise(Total_Num_Records = n(),Unique_CustomerIDs = n_distinct(customerID))

From the below frequency distributions we can see that there are no duplicate values in any categorical variable. The data appears to be clean.

#Customer ID related information has already been check above. It is not required here.
telco_customer_churn_transformed_data %>%
     select(-customerID) %>%
     plot_bar(ggtheme = theme_light(), title = "Categorical Variable Distributions")

From the below histograms we can see that there are some customers with a tenure of 0 months. We assume that these are new customers, added in the past month.

Also, notice that Total Charges is skewed to the right, which makes sense as we have customers with high tenure. The total charges for them will have accumulated for a long period.

telco_customer_churn_transformed_data %>%
     plot_histogram(ggtheme = theme_light())

The missing values of “TotalCharges” that we noticed in earlier sections is because of the new customers (tenure=0). Since they have not completed a billing cycle yet, their Total Charge value is missing. We can confirm this with the result below, which checks if there are any missing TotalCharges values for observations with non-zero tenures. As we can see there are none. So, we can conclude that missing TotalCharges are because of 0 tenure.

telco_customer_churn_transformed_data %>%
     filter(is.na(TotalCharges) && tenure!=0)

Let’s convert missing TotalCharges to 0 and plot missing values again.

#Check if TotalCharges is NA and replace with 0
telco_customer_churn_cleaned_data <- telco_customer_churn_transformed_data %>%
     mutate(TotalCharges = ifelse(is.na(TotalCharges),0,TotalCharges))
telco_customer_churn_cleaned_data %>%
     plot_missing()

Before proceeding to modeling, let’s check if there are any trends in the data.

The overall categorical variables in this data set can be classified as follows:

Let’s check how these variables affect Churn by plotting them

Customer Atrributes


From the graphs below we can notice that:

#using plotly here just to explore the functinalities in this package more
#Plot by SeniorCitizen
telco_customer_churn_cleaned_data %>%
     group_by(Churn,SeniorCitizen) %>%
     summarise(count = n()) %>%
     plot_ly(x= ~SeniorCitizen, y= ~count, name= ~Churn, type = 'bar', width = 400, height = 400) %>%
     layout(title = 'Senior Citizen distribution based on Churn')

#Plot by Gender
telco_customer_churn_cleaned_data %>%
     group_by(Churn,gender) %>%
     summarise(count = n()) %>%
     plot_ly(x= ~gender, y= ~count, name= ~Churn, type = 'bar', width = 400, height = 400) %>%
     layout(title = 'Gender distribution based on Churn')

#Plot by Dependents
telco_customer_churn_cleaned_data %>%
     group_by(Churn,Dependents) %>%
     summarise(count = n()) %>%
     plot_ly(x= ~Dependents, y= ~count, name= ~Churn, type = 'bar', width = 400, height = 400) %>%
     layout(title = 'Dependent distribution based on Churn')

    
#Plot by Partner
telco_customer_churn_cleaned_data %>%
     group_by(Churn,Partner) %>%
     summarise(count = n()) %>%
     plot_ly(x= ~Partner, y= ~count, name= ~Churn, type = 'bar', width = 400, height = 400) %>%
     layout(title = 'Partner distribution based on Churn')

Service Atrributes


From the plots below we can infer the following:

#using ggplot here just to explore the functinalities in this package more
#Plot by SeniorCitizen
p1 <- telco_customer_churn_cleaned_data %>%
     group_by(Churn,PhoneService) %>%
     summarise(count = n()) %>%
     ggplot(aes(PhoneService,count,fill = Churn))+
     geom_bar(stat = "identity", position = "dodge")+
     coord_flip()+
     ggtitle("PhoneService distribution") +
    labs(
        x = "Phone Service",
        y = "Frequency"
    ) +
    theme(
        plot.title = element_text(hjust = 0.5, face = "bold"),
        plot.background = element_rect(
            fill = "white",
            colour = "lightgrey",
            size = 0.75),
        axis.title.x = element_text(face = "bold"),
        axis.title.y = element_text(face = "bold"),
        panel.background = element_rect(fill = "white"),
        panel.grid.major.x = element_line(colour = "#C4DFE6", size = 0.05)
    )
#Plot by Gender
p2 <- telco_customer_churn_cleaned_data %>%
     group_by(Churn,MultipleLines) %>%
     summarise(count = n()) %>%
     ggplot(aes(MultipleLines,count,fill = Churn))+
     geom_bar(stat = "identity", position = "dodge")+
     coord_flip()+
     ggtitle("MultipleLines distribution") +
    labs(
        x = "Multiple Lines",
        y = "Frequency"
    ) +
    theme(
        plot.title = element_text(hjust = 0.5, face = "bold"),
        plot.background = element_rect(
            fill = "white",
            colour = "lightgrey",
            size = 0.75),
        axis.title.x = element_text(face = "bold"),
        axis.title.y = element_text(face = "bold"),
        panel.background = element_rect(fill = "white"),
        panel.grid.major.x = element_line(colour = "#C4DFE6", size = 0.05)
    )
#Plot by Dependents
p3 <- telco_customer_churn_cleaned_data %>%
     group_by(Churn,InternetService) %>%
     summarise(count = n()) %>%
     ggplot(aes(InternetService,count,fill = Churn))+
     geom_bar(stat = "identity", position = "dodge")+
     coord_flip()+
     ggtitle("InternetService distribution") +
    labs(
        x = "Internet Service",
        y = "Frequency"
    ) +
    theme(
        plot.title = element_text(hjust = 0.5, face = "bold"),
        plot.background = element_rect(
            fill = "white",
            colour = "lightgrey",
            size = 0.75),
        axis.title.x = element_text(face = "bold"),
        axis.title.y = element_text(face = "bold"),
        panel.background = element_rect(fill = "white"),
        panel.grid.major.x = element_line(colour = "#C4DFE6", size = 0.05)
    )
#Plot by OnlineSecurity
p4 <- telco_customer_churn_cleaned_data %>%
     group_by(Churn,OnlineSecurity) %>%
     summarise(count = n()) %>%
     ggplot(aes(OnlineSecurity,count,fill = Churn))+
     geom_bar(stat = "identity", position = "dodge")+
     coord_flip()+
     ggtitle("OnlineSecurity distribution") +
    labs(
        x = "Online Security",
        y = "Frequency"
    ) +
    theme(
        plot.title = element_text(hjust = 0.5, face = "bold"),
        plot.background = element_rect(
            fill = "white",
            colour = "lightgrey",
            size = 0.75),
        axis.title.x = element_text(face = "bold"),
        axis.title.y = element_text(face = "bold"),
        panel.background = element_rect(fill = "white"),
        panel.grid.major.x = element_line(colour = "#C4DFE6", size = 0.05)
    )
#Plot by OnlineBackup
p5 <- telco_customer_churn_cleaned_data %>%
     group_by(Churn,OnlineBackup) %>%
     summarise(count = n()) %>%
     ggplot(aes(OnlineBackup,count,fill = Churn))+
     geom_bar(stat = "identity", position = "dodge")+
     coord_flip()+
     ggtitle("OnlineBackup distribution") +
    labs(
        x = "Online Backup",
        y = "Frequency"
    ) +
    theme(
        plot.title = element_text(hjust = 0.5, face = "bold"),
        plot.background = element_rect(
            fill = "white",
            colour = "lightgrey",
            size = 0.75),
        axis.title.x = element_text(face = "bold"),
        axis.title.y = element_text(face = "bold"),
        panel.background = element_rect(fill = "white"),
        panel.grid.major.x = element_line(colour = "#C4DFE6", size = 0.05)
    )
#Plot by DeviceProtection
p6 <- telco_customer_churn_cleaned_data %>%
     group_by(Churn,DeviceProtection) %>%
     summarise(count = n()) %>%
     ggplot(aes(DeviceProtection,count,fill = Churn))+
     geom_bar(stat = "identity", position = "dodge")+
     coord_flip()+
     ggtitle("DeviceProtection distribution") +
    labs(
        x = "Device Protection",
        y = "Frequency"
    ) +
    theme(
        plot.title = element_text(hjust = 0.5, face = "bold"),
        plot.background = element_rect(
            fill = "white",
            colour = "lightgrey",
            size = 0.75),
        axis.title.x = element_text(face = "bold"),
        axis.title.y = element_text(face = "bold"),
        panel.background = element_rect(fill = "white"),
        panel.grid.major.x = element_line(colour = "#C4DFE6", size = 0.05)
    )
#Plot by TechSupport
p7 <- telco_customer_churn_cleaned_data %>%
     group_by(Churn,TechSupport) %>%
     summarise(count = n()) %>%
     ggplot(aes(TechSupport,count,fill = Churn))+
     geom_bar(stat = "identity", position = "dodge")+
     coord_flip()+
     ggtitle("TechSupport distribution") +
    labs(
        x = "Tech Support",
        y = "Frequency"
    ) +
    theme(
        plot.title = element_text(hjust = 0.5, face = "bold"),
        plot.background = element_rect(
            fill = "white",
            colour = "lightgrey",
            size = 0.75),
        axis.title.x = element_text(face = "bold"),
        axis.title.y = element_text(face = "bold"),
        panel.background = element_rect(fill = "white"),
        panel.grid.major.x = element_line(colour = "#C4DFE6", size = 0.05)
    )
#Plot by StreamingTV
p8 <- telco_customer_churn_cleaned_data %>%
     group_by(Churn,StreamingTV) %>%
     summarise(count = n()) %>%
     ggplot(aes(StreamingTV,count,fill = Churn))+
     geom_bar(stat = "identity", position = "dodge")+
     coord_flip()+
     ggtitle("StreamingTV distribution") +
    labs(
        x = "StreamingTV",
        y = "Frequency"
    ) +
    theme(
        plot.title = element_text(hjust = 0.5, face = "bold"),
        plot.background = element_rect(
            fill = "white",
            colour = "lightgrey",
            size = 0.75),
        axis.title.x = element_text(face = "bold"),
        axis.title.y = element_text(face = "bold"),
        panel.background = element_rect(fill = "white"),
        panel.grid.major.x = element_line(colour = "#C4DFE6", size = 0.05)
    )
#Plot by StreamingMovies
p9 <- telco_customer_churn_cleaned_data %>%
     group_by(Churn,StreamingMovies) %>%
     summarise(count = n()) %>%
     ggplot(aes(StreamingMovies,count,fill = Churn))+
     geom_bar(stat = "identity", position = "dodge")+
     coord_flip()+
     ggtitle("StreamingMovies distribution") +
    labs(
        x = "Streaming Movies",
        y = "Frequency"
    ) +
    theme(
        plot.title = element_text(hjust = 0.5, face = "bold"),
        plot.background = element_rect(
            fill = "white",
            colour = "lightgrey",
            size = 0.75),
        axis.title.x = element_text(face = "bold"),
        axis.title.y = element_text(face = "bold"),
        panel.background = element_rect(fill = "white"),
        panel.grid.major.x = element_line(colour = "#C4DFE6", size = 0.05)
    )
#Plot by PaperlessBilling
p10 <- telco_customer_churn_cleaned_data %>%
     group_by(Churn,PaperlessBilling) %>%
     summarise(count = n()) %>%
     ggplot(aes(PaperlessBilling,count,fill = Churn))+
     geom_bar(stat = "identity", position = "dodge")+
     coord_flip()+
     ggtitle("PaperlessBilling distribution") +
    labs(
        x = "Paperless Billing",
        y = "Frequency"
    ) +
    theme(
        plot.title = element_text(hjust = 0.5, face = "bold"),
        plot.background = element_rect(
            fill = "white",
            colour = "lightgrey",
            size = 0.75),
        axis.title.x = element_text(face = "bold"),
        axis.title.y = element_text(face = "bold"),
        panel.background = element_rect(fill = "white"),
        panel.grid.major.x = element_line(colour = "#C4DFE6", size = 0.05)
    )
#Plot by Contract
p11 <- telco_customer_churn_cleaned_data %>%
     group_by(Churn,Contract) %>%
     summarise(count = n()) %>%
     ggplot(aes(Contract,count,fill = Churn))+
     geom_bar(stat = "identity", position = "dodge")+
     coord_flip()+
     ggtitle("Contract Type distribution") +
    labs(
        x = "Contract Type",
        y = "Frequency"
    ) +
    theme(
        plot.title = element_text(hjust = 0.5, face = "bold"),
        plot.background = element_rect(
            fill = "white",
            colour = "lightgrey",
            size = 0.75),
        axis.title.x = element_text(face = "bold"),
        axis.title.y = element_text(face = "bold"),
        panel.background = element_rect(fill = "white"),
        panel.grid.major.x = element_line(colour = "#C4DFE6", size = 0.05)
    )
#Plot by PaymentMethod
p12 <- telco_customer_churn_cleaned_data %>%
     group_by(Churn,PaymentMethod) %>%
     summarise(count = n()) %>%
     ggplot(aes(PaymentMethod,count,fill = Churn))+
     geom_bar(stat = "identity", position = "dodge")+
     coord_flip()+
     ggtitle("PaymentMethod distribution") +
    labs(
        x = "Payment Method",
        y = "Frequency"
    ) +
    theme(
        plot.title = element_text(hjust = 0.5, face = "bold"),
        plot.background = element_rect(
            fill = "white",
            colour = "lightgrey",
            size = 0.75),
        axis.title.x = element_text(face = "bold"),
        axis.title.y = element_text(face = "bold"),
        panel.background = element_rect(fill = "white"),
        panel.grid.major.x = element_line(colour = "#C4DFE6", size = 0.05)
    )
grid.arrange(p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11,p12)

Other Attributes


Let’s look at the following variables in this section:

We can notice that customers who churned were predominantly from lower tenure groups. So, this could be an important variable for our modeling. Overallcharges also has the same distribution with respect to churn, but that could be because it has strong correlation with tenure (OverallCharges = Tenure x MonthlyCharges).

#Tenure
telco_customer_churn_cleaned_data %>%
      plot_ly(x= ~tenure, name = ~Churn, type = 'histogram', showlegend = T, width = 500, height = 400) %>%
     layout(title = 'Tenure distribution by Churn')

#Monthly Charges
telco_customer_churn_cleaned_data %>%
      plot_ly(x= ~MonthlyCharges, name = ~Churn, type = 'histogram', showlegend = T, width = 500, height = 400) %>%
     layout(title = 'Monthly Charges distribution by Churn')

#Overall Charges
telco_customer_churn_cleaned_data %>%
      plot_ly(x= ~TotalCharges, name = ~Churn, type = 'histogram', showlegend = T, width = 500, height = 400) %>%
     layout(title = 'Total Charges distribution by Churn')

By plotting Tenure against MonthlyCharges, we can notice in the below graph that churned customers are mostly concentrated in the low tenure + high monthly charges region. These two variables could be useful in our model.

telco_customer_churn_cleaned_data %>%
     plot_ly(x = ~tenure, y = ~MonthlyCharges, name = ~Churn, width = 1000, height = 500) %>%
     layout(title = 'Monthly Charges vs Tenure by Churn')

By plotting Tenure against TotalCharges, we can notice in the below graph that churned customers are mostly concentrated in the low tenure + low TotalCharges region.

telco_customer_churn_cleaned_data %>%
     plot_ly(x = ~tenure, y = ~TotalCharges, name = ~Churn, width = 1000, height = 500) %>%
     layout(title = 'Total Charges vs Tenure by Churn')

Plotting Monthly Charges against TotalCharges may not be very useful, but let’s see if the plot can give us some insight. As we can see churned customers are in the low-side of TotalCharges (these could be customers with low tenures).

telco_customer_churn_cleaned_data %>%
     plot_ly(x = ~MonthlyCharges, y = ~TotalCharges, name = ~Churn, width = 1000, height = 500) %>%
     layout(title = 'Monthly Charges vs Total Charges by Churn')

Let’s plot the distribution of categorical variables specifically in churned customers to check if any variable stands out.

Customer Attributes distribution on churned customers


We saw earlier that Senior Citizen and Dependent variables could have correlation with Churn, let’s see what is the distribution of these variables in churned customers. As we can see, about 25% of the churned customers are senior citizens and 17.4 percent are those with dependents. So, these attributes may not be as useful as we thought they are afterall.

telco_customer_churn_cleaned_data %>%
      filter(Churn==1) %>%
      plot_ly(labels = ~SeniorCitizen, values = ~Churn, type = "pie", width = 500, height = 400) %>%
      layout(title = 'SeniorCitizens distribution in Churned Customers')

telco_customer_churn_cleaned_data %>%
      filter(Churn==1) %>%
      plot_ly(labels = ~Dependents, values = ~Churn, type = "pie", width = 600, height = 400) %>%
      layout(title = 'Dependent distribution in churned customers')

Service Attributes distribution on churned customers


We saw earlier that Payment Method, Contract Type, Paperless Billing and Internet Service Type attributes could have correlation with Churn, let’s see what is the distribution of these variables in churned customers.

As we can notice from the graphs below, all the 4 variables have a specific category that contributes to more than 50% of churned customers. These variables could be very useful for our models.

telco_customer_churn_cleaned_data %>%
      filter(Churn==1) %>%
      plot_ly(labels = ~PaymentMethod, values = ~Churn, type = "pie", width = 600, height = 400) %>%
      layout(title = 'Payment Method distribution in Churned Customers')

telco_customer_churn_cleaned_data %>%
      filter(Churn==1) %>%
      plot_ly(labels = ~Contract, values = ~Churn, type = "pie", width = 600, height = 400) %>%
      layout(title = 'ContractType distribution in churned customers')

telco_customer_churn_cleaned_data %>%
      filter(Churn==1) %>%
      plot_ly(labels = ~PaperlessBilling, values = ~Churn, type = "pie", width = 600, height = 400) %>%
      layout(title = 'PaperlessBilling distribution in churned customers')

telco_customer_churn_cleaned_data %>%
      filter(Churn==1) %>%
      plot_ly(labels = ~InternetService, values = ~Churn, type = "pie", width = 600, height = 400) %>%
      layout(title = 'InternetService distribution in churned customers')

Results of EDA


Based on the trends we noticed in EDA, we found that the following variables could be very important to predict customer churn

Notice that these are all service attributes and not customer attributes. May be the company must look at service attributes to control churn more than customemr attributes. This could be a good general guiding principle for the company. Let’s proceed to modeling now and use these attributes.

Modeling


Dummify the data so that categorical variables are converted to multiple columns with 0/1s.

telco_customer_churn_dummified <- telco_customer_churn_cleaned_data %>%
                                        dummify() %>%
                                        mutate(Churn_yes = as.factor(ifelse(Churn_1==1, "Yes", "No")))

Partition data into Training and Validation datasets with a 70-30 split of the full dataset.

set.seed(10)
inTrainingData <- createDataPartition(telco_customer_churn_dummified$Churn_yes, p=0.70, list=FALSE)
training.set <- telco_customer_churn_dummified[inTrainingData,]
Totalvalidation.set <- telco_customer_churn_dummified[-inTrainingData,]

Random Forest


Let’s try some mtry values and see which value gives us a lower OOB error percentage.

After repeated trials, mtry 5 was found to give the lowest OOB error of around 21% as shown below.

As we can see from the Variable Importance Plots below, the most important variables for predicting Churn are

  • tenure
  • TotalCharges
  • MonthlyCharges
  • Contract_Month.to.month
  • InternetServuce_Fiber.optic
  • PaymentMethod.Electronic.check

which is pretty much consistent with what we found in our EDA.

training.set <- training.set %>%
                    select(-customerID, -Churn_0, -Churn_1) 
#rf_fit_check <- randomForest(Churn_yes ~., data = training.set, importance = TRUE, metric = "ROC", ntree = 1001, mtry = 2)
#rf_fit_check <- randomForest(Churn_yes ~., data = training.set, importance = TRUE, metric = "ROC", ntree = 1001, mtry = 3)
#rf_fit_check <- randomForest(Churn_yes ~., data = training.set, importance = TRUE, metric = "ROC", ntree = 1001, mtry = 4)
rf_fit_check <- randomForest(Churn_yes ~., data = training.set, importance = TRUE, metric = "roc", ntree = 1001, mtry = 5)
VariableImp

Call:
 randomForest(formula = Churn_yes ~ ., data = training.set, importance = TRUE,      metric = "ROC", ntree = 1001, mtry = 5) 
               Type of random forest: classification
                     Number of trees: 1001
No. of variables tried at each split: 5

        OOB estimate of  error rate: 20.79%
Confusion matrix:
      No Yes class.error
No  3242 380   0.1049144
Yes  645 664   0.4927426
varImpPlot(VariableImp)

Let’s use these variable and build a Random Forest model.

rf_fit <- train(Churn_yes ~ tenure+
                     MonthlyCharges+
                     TotalCharges+
                     Contract_Month.to.month+
                     InternetService_Fiber.optic,
                data = training.set,
                method = "rf",
                metric = "Roc",
                tunegrid=tunegrid,
                trControl=control,
                preProcess = c("center", "scale"),
                ntree=1001)
The metric "Roc" was not in the result set. ROC will be used instead.

Let’s look at the model stistics of the Random Forest model.

As we can see the best ROC was achieved at mtry = 2. The model has high number of false negatives (number of churned customers who were classified as Not Churned). Let’s see if Logistic Regression can give us better predictions in the next section.

print(rf_fit)
Random Forest 

4931 samples
   5 predictor
   2 classes: 'No', 'Yes' 

Pre-processing: centered (5), scaled (5) 
Resampling: Cross-Validated (10 fold, repeated 3 times) 
Summary of sample sizes: 4438, 4437, 4438, 4437, 4439, 4438, ... 
Resampling results across tuning parameters:

  mtry  ROC        Sens       Spec     
  2     0.8205527  0.9008828  0.4702877
  3     0.8012674  0.8728120  0.4937385
  5     0.7946440  0.8726291  0.4825328

ROC was used to select the optimal model using the largest value.
The final value used for the model was mtry = 2.
plot(rf_fit)

pred_test <- predict(rf_fit,newdata = Totalvalidation.set)
confusionMatrix(data=pred_test, Totalvalidation.set$Churn_yes)
Confusion Matrix and Statistics

          Reference
Prediction   No  Yes
       No  1417  305
       Yes  135  255
                                          
               Accuracy : 0.7917          
                 95% CI : (0.7737, 0.8088)
    No Information Rate : 0.7348          
    P-Value [Acc > NIR] : 7.793e-10       
                                          
                  Kappa : 0.408           
 Mcnemar's Test P-Value : 7.834e-16       
                                          
            Sensitivity : 0.9130          
            Specificity : 0.4554          
         Pos Pred Value : 0.8229          
         Neg Pred Value : 0.6538          
             Prevalence : 0.7348          
         Detection Rate : 0.6709          
   Detection Prevalence : 0.8153          
      Balanced Accuracy : 0.6842          
                                          
       'Positive' Class : No              
                                          
results_1 <- confusionMatrix(data=pred_test, Totalvalidation.set$Churn_yes)
confusion_mat1 <- as.data.frame(results_1$table)
ggplot(data =  confusion_mat1, mapping = aes(x = Reference, y = Prediction)) +
  ggtitle("Confusion Matrix for Random Forest") +
  geom_tile(aes(fill = Freq), colour = "white") +
  geom_text(aes(label = sprintf("%1.0f", Freq)), vjust = 1) +
  scale_fill_gradient(low = "#B2DBD5", high = "#2B616D") +
  theme_bw() + theme(legend.position = "none", plot.title = element_text(hjust = 0.5, face = "bold"))

Logistic Regression


Let’s try to find out which predictors are statistically significant for a GLM model.

As we can see, the following variables are statistically significant in this model:

  • tenure
  • totalCharges
  • Contract_Month.to.month
  • PaperlessBilling_No
  • PaymentMethod_Electronic.check

as they have low P-values. This is almost similar to what we found in Random Forest, except that MonthlyCharges has not been found statistically significant here.

summary(glm_fit_check)

Call:
glm(formula = Churn_yes ~ ., family = "binomial", data = training.set)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-1.9221  -0.6631  -0.2966   0.7216   3.3557  

Coefficients: (16 not defined because of singularities)
                                          Estimate Std. Error z value Pr(>|z|)    
(Intercept)                              1.462e+00  2.480e+00   0.589 0.555573    
tenure                                  -5.652e-02  7.249e-03  -7.797 6.32e-15 ***
MonthlyCharges                          -6.236e-02  3.798e-02  -1.642 0.100580    
TotalCharges                             3.005e-04  8.235e-05   3.649 0.000263 ***
gender_Female                            4.064e-03  7.763e-02   0.052 0.958253    
gender_Male                                     NA         NA      NA       NA    
SeniorCitizen_No                        -2.301e-01  1.017e-01  -2.262 0.023702 *  
SeniorCitizen_Yes                               NA         NA      NA       NA    
Partner_No                               2.341e-02  9.347e-02   0.250 0.802275    
Partner_Yes                                     NA         NA      NA       NA    
Dependents_No                            5.765e-02  1.067e-01   0.540 0.588970    
Dependents_Yes                                  NA         NA      NA       NA    
PhoneService_No                         -6.026e-01  7.765e-01  -0.776 0.437748    
PhoneService_Yes                                NA         NA      NA       NA    
MultipleLines_No                        -5.422e-01  2.130e-01  -2.546 0.010894 *  
MultipleLines_Yes                               NA         NA      NA       NA    
InternetService_DSL                      2.276e+00  9.662e-01   2.355 0.018514 *  
InternetService_Fiber.optic              4.616e+00  1.907e+00   2.421 0.015486 *  
InternetService_No                              NA         NA      NA       NA    
OnlineSecurity_No                        9.031e-02  2.135e-01   0.423 0.672335    
OnlineSecurity_Yes                              NA         NA      NA       NA    
OnlineBackup_No                         -1.312e-01  2.098e-01  -0.625 0.531761    
OnlineBackup_Yes                                NA         NA      NA       NA    
DeviceProtection_No                     -2.724e-01  2.099e-01  -1.298 0.194367    
DeviceProtection_Yes                            NA         NA      NA       NA    
TechSupport_No                           1.323e-01  2.148e-01   0.616 0.537858    
TechSupport_Yes                                 NA         NA      NA       NA    
StreamingTV_No                          -7.836e-01  3.912e-01  -2.003 0.045192 *  
StreamingTV_Yes                                 NA         NA      NA       NA    
StreamingMovies_No                      -7.989e-01  3.905e-01  -2.046 0.040777 *  
StreamingMovies_Yes                             NA         NA      NA       NA    
Contract_Month.to.month                  1.271e+00  1.989e-01   6.391 1.64e-10 ***
Contract_One.year                        6.094e-01  2.002e-01   3.044 0.002335 ** 
Contract_Two.year                               NA         NA      NA       NA    
PaperlessBilling_No                     -4.228e-01  8.977e-02  -4.710 2.48e-06 ***
PaperlessBilling_Yes                            NA         NA      NA       NA    
PaymentMethod_Bank.transfer..automatic.  7.921e-02  1.381e-01   0.573 0.566389    
PaymentMethod_Credit.card..automatic.   -4.723e-02  1.405e-01  -0.336 0.736698    
PaymentMethod_Electronic.check           4.104e-01  1.157e-01   3.549 0.000387 ***
PaymentMethod_Mailed.check                      NA         NA      NA       NA    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 5707.1  on 4930  degrees of freedom
Residual deviance: 4085.4  on 4907  degrees of freedom
AIC: 4133.4

Number of Fisher Scoring iterations: 6

Let;s build a regression model using these variables and check how they predict churn.

training.set.2 <- training.set %>%
                    mutate(Churn_yes = as.factor(ifelse(Churn_yes=="Yes",1,0)))
Totalvalidation.set.2 <- Totalvalidation.set %>%
                    mutate(Churn_yes = as.factor(ifelse(Churn_yes=="Yes",1,0)))
control_reg <- trainControl(method = "repeatedcv", number = 10, repeats = 3)
set.seed(10)
reg_fit <- train(Churn_yes ~ tenure+
                             TotalCharges+
                             Contract_Month.to.month+
                             PaperlessBilling_No+
                             PaymentMethod_Electronic.check+
                             Contract_One.year,
                         data = training.set.2,
                         method = "glm",
                         family = "binomial",
                         preProcess = c("center","scale"),
                         trControl = control_reg)

As we can see the accuracy level has dropped to 77%.

print(reg_fit)
Generalized Linear Model 

4931 samples
   6 predictor
   2 classes: '0', '1' 

Pre-processing: centered (6), scaled (6) 
Resampling: Cross-Validated (10 fold, repeated 3 times) 
Summary of sample sizes: 4437, 4438, 4437, 4438, 4439, 4438, ... 
Resampling results:

  Accuracy   Kappa    
  0.7838158  0.4029077
pred_test_reg <- predict(reg_fit,newdata = Totalvalidation.set.2)
confusionMatrix(data=pred_test_reg, Totalvalidation.set.2$Churn_yes)
Confusion Matrix and Statistics

          Reference
Prediction    0    1
         0 1383  307
         1  169  253
                                          
               Accuracy : 0.7746          
                 95% CI : (0.7562, 0.7923)
    No Information Rate : 0.7348          
    P-Value [Acc > NIR] : 1.437e-05       
                                          
                  Kappa : 0.3722          
 Mcnemar's Test P-Value : 3.399e-10       
                                          
            Sensitivity : 0.8911          
            Specificity : 0.4518          
         Pos Pred Value : 0.8183          
         Neg Pred Value : 0.5995          
             Prevalence : 0.7348          
         Detection Rate : 0.6548          
   Detection Prevalence : 0.8002          
      Balanced Accuracy : 0.6714          
                                          
       'Positive' Class : 0               
                                          
results_2 <- confusionMatrix(data=pred_test_reg, Totalvalidation.set.2$Churn_yes)
confusion_mat2 <- as.data.frame(results_2$table)
ggplot(data =  confusion_mat2, mapping = aes(x = Reference, y = Prediction)) +
  ggtitle("Confusion Matrix for Regression Iteration 1") +
  geom_tile(aes(fill = Freq), colour = "white") +
  geom_text(aes(label = sprintf("%1.0f", Freq)), vjust = 1) +
  scale_fill_gradient(low = "#B2DBD5", high = "#2B616D") +
  theme_bw() + theme(legend.position = "none", plot.title = element_text(hjust = 0.5, face = "bold"))

Let’s try to run regression using the same variables that we used in Random Forest and check the results

control_reg_2 <- trainControl(method = "repeatedcv", number = 10, repeats = 3)
set.seed(10)
reg_fit_2 <- train(Churn_yes ~ tenure+
                               MonthlyCharges+
                               TotalCharges+
                               Contract_Month.to.month+
                               InternetService_Fiber.optic,
                         data = training.set.2,
                         method = "glm",
                         family = "binomial",
                         preProcess = c("center","scale"),
                         trControl = control_reg)

As we can see the accuracy level is still 77%. And the false negatives are still on the higher side.

print(reg_fit_2)
Generalized Linear Model 

4931 samples
   5 predictor
   2 classes: '0', '1' 

Pre-processing: centered (5), scaled (5) 
Resampling: Cross-Validated (10 fold, repeated 3 times) 
Summary of sample sizes: 4438, 4439, 4438, 4438, 4438, 4438, ... 
Resampling results:

  Accuracy   Kappa    
  0.7864541  0.4129474
pred_test_reg_2 <- predict(reg_fit_2,newdata = Totalvalidation.set.2)
confusionMatrix(data=pred_test_reg, Totalvalidation.set.2$Churn_yes)
Confusion Matrix and Statistics

          Reference
Prediction    0    1
         0 1383  307
         1  169  253
                                          
               Accuracy : 0.7746          
                 95% CI : (0.7562, 0.7923)
    No Information Rate : 0.7348          
    P-Value [Acc > NIR] : 1.437e-05       
                                          
                  Kappa : 0.3722          
 Mcnemar's Test P-Value : 3.399e-10       
                                          
            Sensitivity : 0.8911          
            Specificity : 0.4518          
         Pos Pred Value : 0.8183          
         Neg Pred Value : 0.5995          
             Prevalence : 0.7348          
         Detection Rate : 0.6548          
   Detection Prevalence : 0.8002          
      Balanced Accuracy : 0.6714          
                                          
       'Positive' Class : 0               
                                          
results_3 <- confusionMatrix(data=pred_test_reg, Totalvalidation.set.2$Churn_yes)
confusion_mat3 <- as.data.frame(results_3$table)
ggplot(data =  confusion_mat3, mapping = aes(x = Reference, y = Prediction)) +
  ggtitle("Confusion Matrix for Regression Iteration 2") +
  geom_tile(aes(fill = Freq), colour = "white") +
  geom_text(aes(label = sprintf("%1.0f", Freq)), vjust = 1) +
  scale_fill_gradient(low = "#B2DBD5", high = "#2B616D") +
  theme_bw() + theme(legend.position = "none", plot.title = element_text(hjust = 0.5, face = "bold"))

Conclusions


Between the two models used, Random Forest is the winner as it has better accuracy.

The following conclusions can be drawn from this analysis:

LS0tDQp0aXRsZTogIkFzc2lnbm1lbnQzIC0gUHJlZGljdCBDdXN0b21lciBDaHVybiINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCiMjI0ludHJvZHVjdGlvbg0KDQotLS0NCg0KVGhlIGFpbSBvZiB0aGlzIGFuYWx5c2lzIGlzIHRvIHByZWRpY3QgY3VzdG9tZXIgY2h1cm4gaW4gYSBUZWxlY29tIGNvbXBhbnkuIFR3byBjbGFzc2lmaWNhdGlvbiBtb2RlbHMgd2lsbCBiZSB1c2VkIHRvIHByZWRpY3QgdGhlIGxpa2VseWhvb2Qgb2YgY3VzdG9tZXIgY2h1cm4uIFRoZSB0d28gbW9kZWxzIHdpbGwgYmUgY29tcGFyZWQgYmFzZWQgb24gdGhlaXIgYWNjdXJhY3kuDQpgYGB7ciBpbmNsdWRlID0gRkFMU0UsIG1lc3NhZ2U9RkFMU0V9DQojVGhlIFIgbGlicmFyaWVzIGxpc3RlZCBiZWxvdyBhcmUgdXNlZCBmb3IgdGhpcyBhbmFseXNpcy4gVGhpcyBibG9jayBsb2FkcyB0aGUgbGlicmFyaWVzIGFuZCByZWFkcyBkYXRhIGZyb20gdGhlIGZpbGUNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShEYXRhRXhwbG9yZXIpDQpsaWJyYXJ5KHBsb3RseSkNCmxpYnJhcnkoY2FyZXQpDQpsaWJyYXJ5KHJhbmRvbUZvcmVzdCkNCmxpYnJhcnkoZ3JpZEV4dHJhKQ0KI3JlYWQgZGF0YSBmaWxlIGZyb20gcHJvamVjdCBkYXRhIGZvbGRlciBwcmVzZW50IGluIHByb2plY3Qgd29ya2luZyBkaXJlY3RvcnkNCnRlbGNvX2N1c3RvbWVyX2NodXJuX3Jhd19kYXRhIDwtIHJlYWRfY3N2KCJkYXRhXFxXQV9Gbi1Vc2VDXy1UZWxjby1DdXN0b21lci1DaHVybi5jc3YiKQ0KYGBgDQoNCiMjI0RhdGEgU2V0DQoNCi0tLQ0KDQpUaGUgZGF0YSBzZXQgY29uc2lzdHMgb2YgNywwNDMgb2JzZXJ2YXRpb25zIG9mIDIxIHZhcmlhYmxlcyBhcyBzaG93biBiZWxvdzogPGJyPg0KDQoqICoqY3VzdG9tZXJJRDoqKiBVbmlxdWUgaWRlbnRpZmllciBmb3IgZWFjaCBjdXN0b21lcg0KKiAqKmdlbmRlcjoqKiBHZW5kZXIgb2YgY3VzdG9tZXINCiogKipTZW5pb3JDaXRpemVuOioqIEEgYmluYXJ5IHZhcmlhYmxlIHdoaWNoIGlkZW50aWZpZXMgaWYgYSBjdXN0b21lciBpcyBhIFNlbmlvciBDaXRpemVuICgxKSBvciBub3QgKDApDQoqICoqUGFydG5lcjoqKiBBIGJpbmFyeSB2YXJpYWJsZSB3aGljaCBpZGVudGlmaWVzIGlmIGEgY3VzdG9tZXIgaGFzIGEgcGFydG5lciAoWWVzL05vKQ0KKiAqKkRlcGVuZGVudHM6KiogQSBiaW5hcnkgdmFyaWFibGUgd2hpY2ggaWRlbnRpZmllcyBpZiBhIGN1c3RvbWVyIGhhcyBhbnkgZGVwZW5kZW50cyAoWWVzL05vKQ0KKiAqKnRlbnVyZToqKiBUaGUgdGVudXJlIG9mIGFzc29jaWF0aW9uIG9mIHRoZSBjdXN0b21lciB3aXRoIHRoZSBjb21wYW55IGluIG1vbnRocw0KKiAqKlBob25lU2VydmljZToqKiBBIGJpbmFyeSB2YXJpYWJsZSB3aGljaCBpZGVudGlmaWVzIGlmIGEgY3VzdG9tZXIgaGFzIHBob25lIHNlcnZpY2UNCiogKipNdWx0aXBsZUxpbmVzOioqIFZhcmlhYmxlIHRoYXQgaWRlbnRmaWVzIGlmIGN1c3RvbWVycyBoYXZpbmcgcGhvbmUgbGluZXMgaGF2ZSBtdWx0aXBsZSBsaW5lcyBvciBub3QNCiogKipJbnRlcm5ldFNlcnZpY2U6KiogVGhpcyB2YXJpYWJsZSBpZGVudGlmaWVzIHRoZSB0eXBlIG9mIGludGVybmV0IHNlcnZpY2UgdGhhdCB0aGUgY3VzdG9tZXIgaGFzDQoqICoqT25saW5lU2VjdXJpdHk6KiogVmFyaWFibGUgdGhhdCBpZGVudGZpZXMgaWYgY3VzdG9tZXJzIGhhdmluZyBpbnRlcm5ldCBzZXJ2aWNlIGhhdmUgb25saW5lIHNlY3VyaXR5IG9yIG5vdA0KKiAqKk9ubGluZWJhY2t1cDoqKiBWYXJpYWJsZSB0aGF0IGlkZW50ZmllcyBpZiBjdXN0b21lcnMgaGF2aW5nIGludGVybmV0IHNlcnZpY2UgaGF2ZSBvbmxpbmUgYmFja3VwIG9yIG5vdA0KKiAqKkRldmljZVByb3RlY3Rpb246KiogVmFyaWFibGUgdGhhdCBpZGVudGZpZXMgaWYgY3VzdG9tZXJzIGhhdmluZyBpbnRlcm5ldCBzZXJ2aWNlIGhhdmUgZGV2aWNlIHByb3RlY3Rpb24gb3Igbm90DQoqICoqVGVjaFN1cHBvcnQ6KiogVmFyaWFibGUgdGhhdCBpZGVudGZpZXMgaWYgY3VzdG9tZXJzIGhhdmluZyBpbnRlcm5ldCBzZXJ2aWNlIGhhdmUgYXZhaWxlZCB0ZWNoIHN1cHBvcnQgb3Igbm90DQoqICoqU3RyZWFtaW5nVFY6KiogVmFyaWFibGUgdGhhdCBpZGVudGZpZXMgaWYgY3VzdG9tZXJzIGhhdmluZyBpbnRlcm5ldCBzZXJ2aWNlIGhhdmUgVFYgU3RyZWFtaW5nIG9yIG5vdA0KKiAqKlN0cmVhbWluZ01vdmllczoqKiBWYXJpYWJsZSB0aGF0IGlkZW50ZmllcyBpZiBjdXN0b21lcnMgaGF2aW5nIGludGVybmV0IHNlcnZpY2UgaGF2ZSBNb3ZpZSBTdHJlYW1pbmcgb3Igbm90DQoqICoqQ29udHJhY3Q6KiogVmFyaWFibGUgdGhhdCBpZGVudGZpZXMgdGhlIGNvbnRyYWN0IHR5cGUgb2YgdGhlIGN1c3RvbWVycw0KKiAqKlBhcGVybGVzc0JpbGxpbmc6KiogVmFyaWFibGUgdGhhdCBpZGVudGZpZXMgaWYgdGhlIGN1c3RvbWVyIGhhcyBvcHRlZCBmb3IgcGFwZXJsZXNzIGJpbGxpbmcNCiogKipQYXltZW50TWV0aG9kOioqIFZhcmlhYmxlIHRoYXQgaWRlbnRmaWVzIHRoZSBwYXltZW50IG1ldGhvZCBvZiB0aGUgY3VzdG9tZXINCiogKipNb250aGx5Q2hhcmdlczoqKiBWYXJpYWJsZSB0aGF0IGlkZW50ZmllcyB0aGUgbW9udGhseSBiaWxsIGFtb3VudCBvZiB0aGUgY3VzdG9tZXINCiogKipUb3RhbENoYXJnZXM6KiogVmFyaWFibGUgdGhhdCBpZGVudGZpZXMgdGhlIG92ZXJhbGwgYmlsbCBhbW91bnQgb2YgdGhlIGN1c3RvbWVyIHRocm91Z2hvdXQgdGhlIHRlbnVyZQ0KKiAqKkNodXJuOioqIFZhcmlhYmxlIHRoYXQgaWRlbnRmaWVzIGlmIHRoZSBjdXN0b21lciBoYXMgY2FuY2VsbGVkIHRoZSBjb21wYW55J3Mgc2VydmljZXM8YnI+DQoNCkxldCdzIGxvb2sgYXQgdGhlIGRpc3RpbmN0IHZhbHVlcyBwcmVzZW50IGluIGVhY2ggY2F0ZWdvcmljYWwgdmFyaWFibGUuDQoNClNvbWUgZmFjdG9yIHZhcmlhYmxlcyBoYXZlIDMgdHlwZXMsIGxldCdzIHNlZSB3aGF0IHRob3NlIHZhbHVlcyBhcmUsIGFuZCBpZiB3ZSBjYW4gZ3JvdXAgdGhlbSBpbiBhbnl3YXkuDQpgYGB7ciBtZXNzYWdlPUZBTFNFfQ0Kb3B0aW9ucyh3aWR0aCA9IDEwKQ0KdGVsY29fY3VzdG9tZXJfY2h1cm5fcmF3X2RhdGEgJT4lDQogICAgIHN1bW1hcmlzZV9hbGwoZnVucyhuX2Rpc3RpbmN0KC4pKSkNCg0KYXMuZGF0YS5mcmFtZSh0YWJsZSh0ZWxjb19jdXN0b21lcl9jaHVybl9yYXdfZGF0YSRNdWx0aXBsZUxpbmVzKSkNCmFzLmRhdGEuZnJhbWUodGFibGUodGVsY29fY3VzdG9tZXJfY2h1cm5fcmF3X2RhdGEkSW50ZXJuZXRTZXJ2aWNlKSkNCmFzLmRhdGEuZnJhbWUodGFibGUodGVsY29fY3VzdG9tZXJfY2h1cm5fcmF3X2RhdGEkT25saW5lU2VjdXJpdHkpKQ0KYXMuZGF0YS5mcmFtZSh0YWJsZSh0ZWxjb19jdXN0b21lcl9jaHVybl9yYXdfZGF0YSRPbmxpbmVCYWNrdXApKQ0KYXMuZGF0YS5mcmFtZSh0YWJsZSh0ZWxjb19jdXN0b21lcl9jaHVybl9yYXdfZGF0YSREZXZpY2VQcm90ZWN0aW9uKSkNCmFzLmRhdGEuZnJhbWUodGFibGUodGVsY29fY3VzdG9tZXJfY2h1cm5fcmF3X2RhdGEkVGVjaFN1cHBvcnQpKQ0KYXMuZGF0YS5mcmFtZSh0YWJsZSh0ZWxjb19jdXN0b21lcl9jaHVybl9yYXdfZGF0YSRTdHJlYW1pbmdNb3ZpZXMpKQ0KYXMuZGF0YS5mcmFtZSh0YWJsZSh0ZWxjb19jdXN0b21lcl9jaHVybl9yYXdfZGF0YSRTdHJlYW1pbmdUVikpDQpgYGAgICAgICANCkFzIHdlIGNhbiBzZWUsIHNvbWUgb2YgdGhlIGZhY3RvcnMgaGF2ZSAzIHZhbHVlcywgYnV0IHRoZSBjYXRlZ29yaWVzICJObyBJbnRlcm5ldCBTZXJ2aWNlIi8iTm8gUGhvbmUgU2VydmljZSIgYXJlIG5vdCBhZGRpbmcgYW55IGFkZGl0aW9uYWwgdmFsdWUgYXMgdGhhdCBpbmZvcm1hdGlvbiBpcyBhbHJlYWR5IGNhcHR1cmVkIGluIGEgc2VwYXJhdGUgdmFyaWFibGUuIFNvLCBsZXQncyB1cGRhdGUgdGhlc2UgdHdvIHZhbHVlcyB0byAiTm8iLg0KDQpBbHNvLCBzaW5jZSBhbGwgY2F0ZWdvcmljYWwgdmFyaWFibGVzIGFyZSBpbiB5ZXMvbm8gZm9ybWF0IGV4Y2VwdCBTZW5pb3IgQ2l0aXplbiwgd2UgY2FuIHRyYW5zZm9ybSB0aGF0IGFsc28gaW50byBZZXMvTm8gZm9ybWF0LiBMZXQncyBhbHNvIHRyYW5zZm9ybSB0aGUgb3V0cHV0IHZhcmlhYmxlIENodXJuIGludG8gMS8wIGZvcm1hdCBzbyB0aGF0IHdlIGRvIG5vdCBoYXZlIHRvIGR1bW1pZnkgaXQgZnVydGhlci4NCmBgYHtyIG1lc3NhZ2U9RkFMU0V9DQojVHJhbnNmb3JtIE11bHRpcGxlTGluZXMgVmFyaWFibGUNCnRlbGNvX2N1c3RvbWVyX2NodXJuX3RyYW5zZm9ybWVkX2RhdGEgPC0gdGVsY29fY3VzdG9tZXJfY2h1cm5fcmF3X2RhdGEgJT4lDQogICAgIG11dGF0ZShNdWx0aXBsZUxpbmVzID0gaWZlbHNlKE11bHRpcGxlTGluZXM9PSJObyBwaG9uZSBzZXJ2aWNlIiwiTm8iLE11bHRpcGxlTGluZXMpKQ0KI1RyYW5zZm9ybSBPbmxpbmVTZWN1cml0eSBWYXJpYWJsZQ0KdGVsY29fY3VzdG9tZXJfY2h1cm5fdHJhbnNmb3JtZWRfZGF0YSA8LSB0ZWxjb19jdXN0b21lcl9jaHVybl90cmFuc2Zvcm1lZF9kYXRhICU+JQ0KICAgICBtdXRhdGUoT25saW5lU2VjdXJpdHkgPSBpZmVsc2UoT25saW5lU2VjdXJpdHk9PSJObyBpbnRlcm5ldCBzZXJ2aWNlIiwiTm8iLE9ubGluZVNlY3VyaXR5KSkNCiNUcmFuc2Zvcm0gT25saWxuZUJhY2t1cCBWYXJpYWJsZQ0KdGVsY29fY3VzdG9tZXJfY2h1cm5fdHJhbnNmb3JtZWRfZGF0YSA8LSB0ZWxjb19jdXN0b21lcl9jaHVybl90cmFuc2Zvcm1lZF9kYXRhICU+JQ0KICAgICBtdXRhdGUoT25saW5lQmFja3VwID0gaWZlbHNlKE9ubGluZUJhY2t1cD09Ik5vIGludGVybmV0IHNlcnZpY2UiLCJObyIsT25saW5lQmFja3VwKSkNCiNUcmFuc2Zvcm0gRGV2aWNlUHJvdGVjdGlvbiBWYXJpYWJsZQ0KdGVsY29fY3VzdG9tZXJfY2h1cm5fdHJhbnNmb3JtZWRfZGF0YSA8LSB0ZWxjb19jdXN0b21lcl9jaHVybl90cmFuc2Zvcm1lZF9kYXRhICU+JQ0KICAgICBtdXRhdGUoRGV2aWNlUHJvdGVjdGlvbiA9IGlmZWxzZShEZXZpY2VQcm90ZWN0aW9uPT0iTm8gaW50ZXJuZXQgc2VydmljZSIsIk5vIixEZXZpY2VQcm90ZWN0aW9uKSkNCiNUcmFuc2Zvcm0gVGVjaFN1cHBvcnQgVmFyaWFibGUNCnRlbGNvX2N1c3RvbWVyX2NodXJuX3RyYW5zZm9ybWVkX2RhdGEgPC0gdGVsY29fY3VzdG9tZXJfY2h1cm5fdHJhbnNmb3JtZWRfZGF0YSAlPiUNCiAgICAgbXV0YXRlKFRlY2hTdXBwb3J0ID0gaWZlbHNlKFRlY2hTdXBwb3J0PT0iTm8gaW50ZXJuZXQgc2VydmljZSIsIk5vIixUZWNoU3VwcG9ydCkpDQojVHJhbnNmb3JtIFN0cmVhbWluZyBNb3ZpZXMgVmFyaWFibGUNCnRlbGNvX2N1c3RvbWVyX2NodXJuX3RyYW5zZm9ybWVkX2RhdGEgPC0gdGVsY29fY3VzdG9tZXJfY2h1cm5fdHJhbnNmb3JtZWRfZGF0YSAlPiUNCiAgICAgbXV0YXRlKFN0cmVhbWluZ01vdmllcyA9IGlmZWxzZShTdHJlYW1pbmdNb3ZpZXM9PSJObyBpbnRlcm5ldCBzZXJ2aWNlIiwiTm8iLFN0cmVhbWluZ01vdmllcykpDQojVHJhbnNmb3JtIFN0cmVhbWluZyBUViBWYXJpYWJsZQ0KdGVsY29fY3VzdG9tZXJfY2h1cm5fdHJhbnNmb3JtZWRfZGF0YSA8LSB0ZWxjb19jdXN0b21lcl9jaHVybl90cmFuc2Zvcm1lZF9kYXRhICU+JQ0KICAgICBtdXRhdGUoU3RyZWFtaW5nVFYgPSBpZmVsc2UoU3RyZWFtaW5nVFY9PSJObyBpbnRlcm5ldCBzZXJ2aWNlIiwiTm8iLFN0cmVhbWluZ1RWKSkNCiNUcmFuc2Zvcm0gU2VuaW9yIENpdGl6ZW4gVmFyaWFibGUNCnRlbGNvX2N1c3RvbWVyX2NodXJuX3RyYW5zZm9ybWVkX2RhdGEgPC0gdGVsY29fY3VzdG9tZXJfY2h1cm5fdHJhbnNmb3JtZWRfZGF0YSAlPiUNCiAgICAgbXV0YXRlKFNlbmlvckNpdGl6ZW4gPSBpZmVsc2UoU2VuaW9yQ2l0aXplbj09MSwiWWVzIiwiTm8iKSkNCiNUcmFuc2Zvcm0gQ2h1cm4gVmFyaWFibGUNCnRlbGNvX2N1c3RvbWVyX2NodXJuX3RyYW5zZm9ybWVkX2RhdGEgPC0gdGVsY29fY3VzdG9tZXJfY2h1cm5fdHJhbnNmb3JtZWRfZGF0YSAlPiUNCiAgICAgbXV0YXRlKENodXJuID0gaWZlbHNlKENodXJuPT0iWWVzIiwxLDApKQ0KDQpgYGANCg0KTGV0J3MgbG9vayBhdCB0aGUgc3RydWN0dXJlIG9mIHRoZSBkYXRhc2V0Lg0KYGBge3IgZmlnLndpZHRoPTgsICBtZXNzYWdlPUZBTFNFfQ0KdGVsY29fY3VzdG9tZXJfY2h1cm5fdHJhbnNmb3JtZWRfZGF0YSAlPiUNCiAgICAgcGxvdF9zdHIoKQ0KYGBgDQpGcm9tIHRoZSBhYm92ZSBncmFwaCB3ZSBjYW4gbm90aWNlIHRoYXQgc29tZSBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgYXJlIGN1cnJlbnRseSAnY2hhcmFjdGVycycuIFdlIGNhbiBjb252ZXJ0IHRoZW0gdG8gZmFjdG9ycyBmb3IgbW9yZSBhY2N1cmF0ZSByZXByZXNlbnRhdGlvbi4NCmBgYHtyICBtZXNzYWdlPUZBTFNFfQ0KdGVsY29fY3VzdG9tZXJfY2h1cm5fdHJhbnNmb3JtZWRfZGF0YSA8LSB0ZWxjb19jdXN0b21lcl9jaHVybl90cmFuc2Zvcm1lZF9kYXRhICU+JQ0KICAgICBtdXRhdGUoU2VuaW9yQ2l0aXplbiA9IGFzLmZhY3RvcihTZW5pb3JDaXRpemVuKSwNCiAgICAgICAgICAgIGdlbmRlciA9IGFzLmZhY3RvcihnZW5kZXIpLA0KICAgICAgICAgICAgUGFydG5lciA9IGFzLmZhY3RvcihQYXJ0bmVyKSwNCiAgICAgICAgICAgIERlcGVuZGVudHMgPSBhcy5mYWN0b3IoRGVwZW5kZW50cyksDQogICAgICAgICAgICBTZW5pb3JDaXRpemVuID0gYXMuZmFjdG9yKFNlbmlvckNpdGl6ZW4pLA0KICAgICAgICAgICAgUGhvbmVTZXJ2aWNlID0gYXMuZmFjdG9yKFBob25lU2VydmljZSksDQogICAgICAgICAgICBNdWx0aXBsZUxpbmVzID0gYXMuZmFjdG9yKE11bHRpcGxlTGluZXMpLA0KICAgICAgICAgICAgSW50ZXJuZXRTZXJ2aWNlID0gYXMuZmFjdG9yKEludGVybmV0U2VydmljZSksDQogICAgICAgICAgICBPbmxpbmVTZWN1cml0eSA9IGFzLmZhY3RvcihPbmxpbmVTZWN1cml0eSksDQogICAgICAgICAgICBPbmxpbmVCYWNrdXAgPSBhcy5mYWN0b3IoT25saW5lQmFja3VwKSwNCiAgICAgICAgICAgIERldmljZVByb3RlY3Rpb24gPSBhcy5mYWN0b3IoRGV2aWNlUHJvdGVjdGlvbiksDQogICAgICAgICAgICBUZWNoU3VwcG9ydCA9IGFzLmZhY3RvcihUZWNoU3VwcG9ydCksDQogICAgICAgICAgICBTdHJlYW1pbmdUViA9IGFzLmZhY3RvcihTdHJlYW1pbmdUViksDQogICAgICAgICAgICBTdHJlYW1pbmdNb3ZpZXMgPSBhcy5mYWN0b3IoU3RyZWFtaW5nTW92aWVzKSwNCiAgICAgICAgICAgIFBhcGVybGVzc0JpbGxpbmcgPSBhcy5mYWN0b3IoUGFwZXJsZXNzQmlsbGluZyksDQogICAgICAgICAgICBDb250cmFjdCA9IGFzLmZhY3RvcihDb250cmFjdCksDQogICAgICAgICAgICBQYXltZW50TWV0aG9kID0gYXMuZmFjdG9yKFBheW1lbnRNZXRob2QpLA0KICAgICAgICAgICAgQ2h1cm4gPSBhcy5mYWN0b3IoQ2h1cm4pKQ0KYGBgDQoNCiMjI0RhdGEgRXhwbG9yYXRpb24NCg0KLS0tDQoNCkFzIHRoZSBncmFwaCBiZWxvdyBzaG93cywgOTkuOCUgb2YgdGhlIG9ic2VydmF0aW9ucyBhcmUgY29tcGxldGUuDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0V9DQp0ZWxjb19jdXN0b21lcl9jaHVybl90cmFuc2Zvcm1lZF9kYXRhICU+JQ0KICAgICBwbG90X2ludHJvKCkNCmBgYA0KVGhlIGJlbG93IGdyYXBoIHNob3dzIHVzIHRoYXQgIlRvdGFsQ2hhcmdlcyIgaGFzIG1pc3NpbmcgdmFsdWVzLiBUaG91Z2ggdGhleSBhcmUgdmVyeSBmZXcsIHdlIHdpbGwgY2hlY2sgdGhlIHJlYXNvbiBiZWhpbmQgdGhpcyBpbiBmdXJ0aGVyIHNlY3Rpb25zLg0KYGBge3IgbWVzc2FnZT1GQUxTRX0NCnRlbGNvX2N1c3RvbWVyX2NodXJuX3RyYW5zZm9ybWVkX2RhdGEgJT4lDQogICAgIHBsb3RfbWlzc2luZyh0aXRsZSA9ICJNaXNzaW5nIHZhbHVlcyBpbiB2YXJpYWJsZXMiKQ0KYGBgDQoNCkZyb20gdGhlIGJlbG93IGNvdW50IHdlIGNhbiBzZWUgdGhhdCB0aGVyZSBhcmUgbm8gZHVwbGljYXRlIEN1c3RvbWVyIElEcyBpbiB0aGUgZGF0YXNldC4gVGhlcmUgYXJlIDcwNDMgcmVjb3JkcyBhbmQgNzA0MyB1bmlxdWUgQ3VzdG9tZXIgSURzDQpgYGB7cn0NCnRlbGNvX2N1c3RvbWVyX2NodXJuX3RyYW5zZm9ybWVkX2RhdGEgJT4lDQogICAgIHN1bW1hcmlzZShUb3RhbF9OdW1fUmVjb3JkcyA9IG4oKSxVbmlxdWVfQ3VzdG9tZXJJRHMgPSBuX2Rpc3RpbmN0KGN1c3RvbWVySUQpKQ0KYGBgDQoNCkZyb20gdGhlIGJlbG93IGZyZXF1ZW5jeSBkaXN0cmlidXRpb25zIHdlIGNhbiBzZWUgdGhhdCB0aGVyZSBhcmUgbm8gZHVwbGljYXRlIHZhbHVlcyBpbiBhbnkgY2F0ZWdvcmljYWwgdmFyaWFibGUuIFRoZSBkYXRhIGFwcGVhcnMgdG8gYmUgY2xlYW4uDQpgYGB7ciBmaWcud2lkdGg9MTAsIG1lc3NhZ2U9RkFMU0V9DQojQ3VzdG9tZXIgSUQgcmVsYXRlZCBpbmZvcm1hdGlvbiBoYXMgYWxyZWFkeSBiZWVuIGNoZWNrIGFib3ZlLiBJdCBpcyBub3QgcmVxdWlyZWQgaGVyZS4NCnRlbGNvX2N1c3RvbWVyX2NodXJuX3RyYW5zZm9ybWVkX2RhdGEgJT4lDQogICAgIHNlbGVjdCgtY3VzdG9tZXJJRCkgJT4lDQogICAgIHBsb3RfYmFyKGdndGhlbWUgPSB0aGVtZV9saWdodCgpLCB0aXRsZSA9ICJDYXRlZ29yaWNhbCBWYXJpYWJsZSBEaXN0cmlidXRpb25zIikNCg0KP3Bsb3RfYmFyDQpgYGANCg0KRnJvbSB0aGUgYmVsb3cgaGlzdG9ncmFtcyB3ZSBjYW4gc2VlIHRoYXQgdGhlcmUgYXJlIHNvbWUgY3VzdG9tZXJzIHdpdGggYSB0ZW51cmUgb2YgMCBtb250aHMuIFdlIGFzc3VtZSB0aGF0IHRoZXNlIGFyZSBuZXcgY3VzdG9tZXJzLCBhZGRlZCBpbiB0aGUgcGFzdCBtb250aC4NCg0KQWxzbywgbm90aWNlIHRoYXQgVG90YWwgQ2hhcmdlcyBpcyBza2V3ZWQgdG8gdGhlIHJpZ2h0LCB3aGljaCBtYWtlcyBzZW5zZSBhcyB3ZSBoYXZlIGN1c3RvbWVycyB3aXRoIGhpZ2ggdGVudXJlLiBUaGUgdG90YWwgY2hhcmdlcyBmb3IgdGhlbSB3aWxsIGhhdmUgYWNjdW11bGF0ZWQgZm9yIGEgbG9uZyBwZXJpb2QuDQpgYGB7ciBmaWcud2lkdGg9NiwgZmlnLmhlaWdodD02fQ0KdGVsY29fY3VzdG9tZXJfY2h1cm5fdHJhbnNmb3JtZWRfZGF0YSAlPiUNCiAgICAgcGxvdF9oaXN0b2dyYW0oZ2d0aGVtZSA9IHRoZW1lX2xpZ2h0KCkpDQpgYGANCg0KVGhlIG1pc3NpbmcgdmFsdWVzIG9mICJUb3RhbENoYXJnZXMiIHRoYXQgd2Ugbm90aWNlZCBpbiBlYXJsaWVyIHNlY3Rpb25zIGlzIGJlY2F1c2Ugb2YgdGhlIG5ldyBjdXN0b21lcnMgKHRlbnVyZT0wKS4gU2luY2UgdGhleSBoYXZlIG5vdCBjb21wbGV0ZWQgYSBiaWxsaW5nIGN5Y2xlIHlldCwgdGhlaXIgVG90YWwgQ2hhcmdlIHZhbHVlIGlzIG1pc3NpbmcuIFdlIGNhbiBjb25maXJtIHRoaXMgd2l0aCB0aGUgcmVzdWx0IGJlbG93LCB3aGljaCBjaGVja3MgaWYgdGhlcmUgYXJlIGFueSBtaXNzaW5nIFRvdGFsQ2hhcmdlcyB2YWx1ZXMgZm9yIG9ic2VydmF0aW9ucyB3aXRoIG5vbi16ZXJvIHRlbnVyZXMuIEFzIHdlIGNhbiBzZWUgdGhlcmUgYXJlIG5vbmUuIFNvLCB3ZSBjYW4gY29uY2x1ZGUgdGhhdCBtaXNzaW5nIFRvdGFsQ2hhcmdlcyBhcmUgYmVjYXVzZSBvZiAwIHRlbnVyZS4NCmBgYHtyfQ0KdGVsY29fY3VzdG9tZXJfY2h1cm5fdHJhbnNmb3JtZWRfZGF0YSAlPiUNCiAgICAgZmlsdGVyKGlzLm5hKFRvdGFsQ2hhcmdlcykgJiYgdGVudXJlIT0wKQ0KYGBgDQoNCkxldCdzIGNvbnZlcnQgbWlzc2luZyBUb3RhbENoYXJnZXMgdG8gMCBhbmQgcGxvdCBtaXNzaW5nIHZhbHVlcyBhZ2Fpbi4NCmBgYHtyfQ0KI0NoZWNrIGlmIFRvdGFsQ2hhcmdlcyBpcyBOQSBhbmQgcmVwbGFjZSB3aXRoIDANCnRlbGNvX2N1c3RvbWVyX2NodXJuX2NsZWFuZWRfZGF0YSA8LSB0ZWxjb19jdXN0b21lcl9jaHVybl90cmFuc2Zvcm1lZF9kYXRhICU+JQ0KICAgICBtdXRhdGUoVG90YWxDaGFyZ2VzID0gaWZlbHNlKGlzLm5hKFRvdGFsQ2hhcmdlcyksMCxUb3RhbENoYXJnZXMpKQ0KDQp0ZWxjb19jdXN0b21lcl9jaHVybl9jbGVhbmVkX2RhdGEgJT4lDQogICAgIHBsb3RfbWlzc2luZygpDQpgYGANCkJlZm9yZSBwcm9jZWVkaW5nIHRvIG1vZGVsaW5nLCBsZXQncyBjaGVjayBpZiB0aGVyZSBhcmUgYW55IHRyZW5kcyBpbiB0aGUgZGF0YS4gDQoNClRoZSBvdmVyYWxsIGNhdGVnb3JpY2FsIHZhcmlhYmxlcyBpbiB0aGlzIGRhdGEgc2V0IGNhbiBiZSBjbGFzc2lmaWVkIGFzIGZvbGxvd3M6DQoNCiogdmFyaWFibGVzIHRoYXQgZGVmaW5lIGF0dHJpYnV0ZXMgb2YgYSBjdXN0b21lcg0KKiB2YXJpYWJsZXMgdGhhdCBkZWZpbmUgYXR0cmlidXRlcyBvZiB0aGUgc2VydmljZQ0KDQpMZXQncyBjaGVjayBob3cgdGhlc2UgdmFyaWFibGVzIGFmZmVjdCBDaHVybiBieSBwbG90dGluZyB0aGVtDQoNCioqQ3VzdG9tZXIgQXRycmlidXRlcyoqDQoNCi0tLQ0KDQpGcm9tIHRoZSBncmFwaHMgYmVsb3cgd2UgY2FuIG5vdGljZSB0aGF0Og0KDQoqIEdlbmRlciBkb2VzIG5vdCBoYXZlIGEgY2xlYXIgdHJlbmQgZm9yIENodXJuOyB0aGUgZ2VuZGVyIHNwbGl0IG9mIGNodXJuZWQgY3VzdG9tZXJzIGlzIG5lYXJseSA1MC01MC4NCiogU2VuaW9yIENpdGl6ZW5zIGFuZCBjdXN0b21lcnMgd2l0aCBkZXBlbmRlbnRzIHNlZW1zIHRvIGhhdmUgbG93ZXIgY2h1cm4sIHdlIGNhbiBjaGVjayB0aGUgZWZmZWN0IG9mIHRoZXNlIHZhcmlhYmxlIG9uIGNodXJuIGZ1cnRoZXIuDQpgYGB7cn0NCiN1c2luZyBwbG90bHkgaGVyZSBqdXN0IHRvIGV4cGxvcmUgdGhlIGZ1bmN0aW5hbGl0aWVzIGluIHRoaXMgcGFja2FnZSBtb3JlDQojUGxvdCBieSBTZW5pb3JDaXRpemVuDQp0ZWxjb19jdXN0b21lcl9jaHVybl9jbGVhbmVkX2RhdGEgJT4lDQogICAgIGdyb3VwX2J5KENodXJuLFNlbmlvckNpdGl6ZW4pICU+JQ0KICAgICBzdW1tYXJpc2UoY291bnQgPSBuKCkpICU+JQ0KICAgICBwbG90X2x5KHg9IH5TZW5pb3JDaXRpemVuLCB5PSB+Y291bnQsIG5hbWU9IH5DaHVybiwgdHlwZSA9ICdiYXInLCB3aWR0aCA9IDQwMCwgaGVpZ2h0ID0gNDAwKSAlPiUNCiAgICAgbGF5b3V0KHRpdGxlID0gJ1NlbmlvciBDaXRpemVuIGRpc3RyaWJ1dGlvbiBiYXNlZCBvbiBDaHVybicpDQoNCiNQbG90IGJ5IEdlbmRlcg0KdGVsY29fY3VzdG9tZXJfY2h1cm5fY2xlYW5lZF9kYXRhICU+JQ0KICAgICBncm91cF9ieShDaHVybixnZW5kZXIpICU+JQ0KICAgICBzdW1tYXJpc2UoY291bnQgPSBuKCkpICU+JQ0KICAgICBwbG90X2x5KHg9IH5nZW5kZXIsIHk9IH5jb3VudCwgbmFtZT0gfkNodXJuLCB0eXBlID0gJ2JhcicsIHdpZHRoID0gNDAwLCBoZWlnaHQgPSA0MDApICU+JQ0KICAgICBsYXlvdXQodGl0bGUgPSAnR2VuZGVyIGRpc3RyaWJ1dGlvbiBiYXNlZCBvbiBDaHVybicpDQoNCiNQbG90IGJ5IERlcGVuZGVudHMNCnRlbGNvX2N1c3RvbWVyX2NodXJuX2NsZWFuZWRfZGF0YSAlPiUNCiAgICAgZ3JvdXBfYnkoQ2h1cm4sRGVwZW5kZW50cykgJT4lDQogICAgIHN1bW1hcmlzZShjb3VudCA9IG4oKSkgJT4lDQogICAgIHBsb3RfbHkoeD0gfkRlcGVuZGVudHMsIHk9IH5jb3VudCwgbmFtZT0gfkNodXJuLCB0eXBlID0gJ2JhcicsIHdpZHRoID0gNDAwLCBoZWlnaHQgPSA0MDApICU+JQ0KICAgICBsYXlvdXQodGl0bGUgPSAnRGVwZW5kZW50IGRpc3RyaWJ1dGlvbiBiYXNlZCBvbiBDaHVybicpDQogICAgDQojUGxvdCBieSBQYXJ0bmVyDQp0ZWxjb19jdXN0b21lcl9jaHVybl9jbGVhbmVkX2RhdGEgJT4lDQogICAgIGdyb3VwX2J5KENodXJuLFBhcnRuZXIpICU+JQ0KICAgICBzdW1tYXJpc2UoY291bnQgPSBuKCkpICU+JQ0KICAgICBwbG90X2x5KHg9IH5QYXJ0bmVyLCB5PSB+Y291bnQsIG5hbWU9IH5DaHVybiwgdHlwZSA9ICdiYXInLCB3aWR0aCA9IDQwMCwgaGVpZ2h0ID0gNDAwKSAlPiUNCiAgICAgbGF5b3V0KHRpdGxlID0gJ1BhcnRuZXIgZGlzdHJpYnV0aW9uIGJhc2VkIG9uIENodXJuJykNCg0KYGBgDQoNCioqU2VydmljZSBBdHJyaWJ1dGVzKioNCg0KLS0tDQoNCkZyb20gdGhlIHBsb3RzIGJlbG93IHdlIGNhbiBpbmZlciB0aGUgZm9sbG93aW5nOg0KDQoqIEN1c3RvbWVycyB3aXRoIEZpYmVyIE9wdGljIEludGVybmV0IFNldmljZSBzZWVtcyB0byBjaHVybiBtb3JlDQoqIEN1c3RvbWVyIHdpdGggTW9udGgtdG8tbW9udGggY29udHJhY3RzIHNlZW1zIHRvIGNodXJuIG1vcmUNCiogQ3VzdG9tZXJzIHdobyBwYXkgdGhlaXIgYmlsbHMgdGhyb3VnaCBFbGVjdHJvbmljIENoZWNrIHNlZW0gdG8gY2h1cm4gbW9yZQ0KKiBDdXN0b21lcnMgd2hvIGhhdmUgb3B0ZWQgZm9yIHBhcGVybGVzcyBiaWxscyBzZWVtIHRvIGNodXJuIG1vcmUNCg0KYGBge3IgZmlnLndpZHRoPTE1LCBmaWcuaGVpZ2h0PTEwfQ0KI3VzaW5nIGdncGxvdCBoZXJlIGp1c3QgdG8gZXhwbG9yZSB0aGUgZnVuY3RpbmFsaXRpZXMgaW4gdGhpcyBwYWNrYWdlIG1vcmUNCiNQbG90IGJ5IFNlbmlvckNpdGl6ZW4NCnAxIDwtIHRlbGNvX2N1c3RvbWVyX2NodXJuX2NsZWFuZWRfZGF0YSAlPiUNCiAgICAgZ3JvdXBfYnkoQ2h1cm4sUGhvbmVTZXJ2aWNlKSAlPiUNCiAgICAgc3VtbWFyaXNlKGNvdW50ID0gbigpKSAlPiUNCiAgICAgZ2dwbG90KGFlcyhQaG9uZVNlcnZpY2UsY291bnQsZmlsbCA9IENodXJuKSkrDQogICAgIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBwb3NpdGlvbiA9ICJkb2RnZSIpKw0KICAgICBjb29yZF9mbGlwKCkrDQogICAgIGdndGl0bGUoIlBob25lU2VydmljZSBkaXN0cmlidXRpb24iKSArDQogICAgbGFicygNCiAgICAgICAgeCA9ICJQaG9uZSBTZXJ2aWNlIiwNCiAgICAgICAgeSA9ICJGcmVxdWVuY3kiDQogICAgKSArDQogICAgdGhlbWUoDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoDQogICAgICAgICAgICBmaWxsID0gIndoaXRlIiwNCiAgICAgICAgICAgIGNvbG91ciA9ICJsaWdodGdyZXkiLA0KICAgICAgICAgICAgc2l6ZSA9IDAuNzUpLA0KICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gIndoaXRlIiksDQogICAgICAgIHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfbGluZShjb2xvdXIgPSAiI0M0REZFNiIsIHNpemUgPSAwLjA1KQ0KICAgICkNCiNQbG90IGJ5IEdlbmRlcg0KcDIgPC0gdGVsY29fY3VzdG9tZXJfY2h1cm5fY2xlYW5lZF9kYXRhICU+JQ0KICAgICBncm91cF9ieShDaHVybixNdWx0aXBsZUxpbmVzKSAlPiUNCiAgICAgc3VtbWFyaXNlKGNvdW50ID0gbigpKSAlPiUNCiAgICAgZ2dwbG90KGFlcyhNdWx0aXBsZUxpbmVzLGNvdW50LGZpbGwgPSBDaHVybikpKw0KICAgICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgcG9zaXRpb24gPSAiZG9kZ2UiKSsNCiAgICAgY29vcmRfZmxpcCgpKw0KICAgICBnZ3RpdGxlKCJNdWx0aXBsZUxpbmVzIGRpc3RyaWJ1dGlvbiIpICsNCiAgICBsYWJzKA0KICAgICAgICB4ID0gIk11bHRpcGxlIExpbmVzIiwNCiAgICAgICAgeSA9ICJGcmVxdWVuY3kiDQogICAgKSArDQogICAgdGhlbWUoDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoDQogICAgICAgICAgICBmaWxsID0gIndoaXRlIiwNCiAgICAgICAgICAgIGNvbG91ciA9ICJsaWdodGdyZXkiLA0KICAgICAgICAgICAgc2l6ZSA9IDAuNzUpLA0KICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gIndoaXRlIiksDQogICAgICAgIHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfbGluZShjb2xvdXIgPSAiI0M0REZFNiIsIHNpemUgPSAwLjA1KQ0KICAgICkNCiNQbG90IGJ5IERlcGVuZGVudHMNCnAzIDwtIHRlbGNvX2N1c3RvbWVyX2NodXJuX2NsZWFuZWRfZGF0YSAlPiUNCiAgICAgZ3JvdXBfYnkoQ2h1cm4sSW50ZXJuZXRTZXJ2aWNlKSAlPiUNCiAgICAgc3VtbWFyaXNlKGNvdW50ID0gbigpKSAlPiUNCiAgICAgZ2dwbG90KGFlcyhJbnRlcm5ldFNlcnZpY2UsY291bnQsZmlsbCA9IENodXJuKSkrDQogICAgIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBwb3NpdGlvbiA9ICJkb2RnZSIpKw0KICAgICBjb29yZF9mbGlwKCkrDQogICAgIGdndGl0bGUoIkludGVybmV0U2VydmljZSBkaXN0cmlidXRpb24iKSArDQogICAgbGFicygNCiAgICAgICAgeCA9ICJJbnRlcm5ldCBTZXJ2aWNlIiwNCiAgICAgICAgeSA9ICJGcmVxdWVuY3kiDQogICAgKSArDQogICAgdGhlbWUoDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoDQogICAgICAgICAgICBmaWxsID0gIndoaXRlIiwNCiAgICAgICAgICAgIGNvbG91ciA9ICJsaWdodGdyZXkiLA0KICAgICAgICAgICAgc2l6ZSA9IDAuNzUpLA0KICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gIndoaXRlIiksDQogICAgICAgIHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfbGluZShjb2xvdXIgPSAiI0M0REZFNiIsIHNpemUgPSAwLjA1KQ0KICAgICkNCiNQbG90IGJ5IE9ubGluZVNlY3VyaXR5DQpwNCA8LSB0ZWxjb19jdXN0b21lcl9jaHVybl9jbGVhbmVkX2RhdGEgJT4lDQogICAgIGdyb3VwX2J5KENodXJuLE9ubGluZVNlY3VyaXR5KSAlPiUNCiAgICAgc3VtbWFyaXNlKGNvdW50ID0gbigpKSAlPiUNCiAgICAgZ2dwbG90KGFlcyhPbmxpbmVTZWN1cml0eSxjb3VudCxmaWxsID0gQ2h1cm4pKSsNCiAgICAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHBvc2l0aW9uID0gImRvZGdlIikrDQogICAgIGNvb3JkX2ZsaXAoKSsNCiAgICAgZ2d0aXRsZSgiT25saW5lU2VjdXJpdHkgZGlzdHJpYnV0aW9uIikgKw0KICAgIGxhYnMoDQogICAgICAgIHggPSAiT25saW5lIFNlY3VyaXR5IiwNCiAgICAgICAgeSA9ICJGcmVxdWVuY3kiDQogICAgKSArDQogICAgdGhlbWUoDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoDQogICAgICAgICAgICBmaWxsID0gIndoaXRlIiwNCiAgICAgICAgICAgIGNvbG91ciA9ICJsaWdodGdyZXkiLA0KICAgICAgICAgICAgc2l6ZSA9IDAuNzUpLA0KICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gIndoaXRlIiksDQogICAgICAgIHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfbGluZShjb2xvdXIgPSAiI0M0REZFNiIsIHNpemUgPSAwLjA1KQ0KICAgICkNCiNQbG90IGJ5IE9ubGluZUJhY2t1cA0KcDUgPC0gdGVsY29fY3VzdG9tZXJfY2h1cm5fY2xlYW5lZF9kYXRhICU+JQ0KICAgICBncm91cF9ieShDaHVybixPbmxpbmVCYWNrdXApICU+JQ0KICAgICBzdW1tYXJpc2UoY291bnQgPSBuKCkpICU+JQ0KICAgICBnZ3Bsb3QoYWVzKE9ubGluZUJhY2t1cCxjb3VudCxmaWxsID0gQ2h1cm4pKSsNCiAgICAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHBvc2l0aW9uID0gImRvZGdlIikrDQogICAgIGNvb3JkX2ZsaXAoKSsNCiAgICAgZ2d0aXRsZSgiT25saW5lQmFja3VwIGRpc3RyaWJ1dGlvbiIpICsNCiAgICBsYWJzKA0KICAgICAgICB4ID0gIk9ubGluZSBCYWNrdXAiLA0KICAgICAgICB5ID0gIkZyZXF1ZW5jeSINCiAgICApICsNCiAgICB0aGVtZSgNCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgZmFjZSA9ICJib2xkIiksDQogICAgICAgIHBsb3QuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdCgNCiAgICAgICAgICAgIGZpbGwgPSAid2hpdGUiLA0KICAgICAgICAgICAgY29sb3VyID0gImxpZ2h0Z3JleSIsDQogICAgICAgICAgICBzaXplID0gMC43NSksDQogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSAid2hpdGUiKSwNCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvci54ID0gZWxlbWVudF9saW5lKGNvbG91ciA9ICIjQzRERkU2Iiwgc2l6ZSA9IDAuMDUpDQogICAgKQ0KI1Bsb3QgYnkgRGV2aWNlUHJvdGVjdGlvbg0KcDYgPC0gdGVsY29fY3VzdG9tZXJfY2h1cm5fY2xlYW5lZF9kYXRhICU+JQ0KICAgICBncm91cF9ieShDaHVybixEZXZpY2VQcm90ZWN0aW9uKSAlPiUNCiAgICAgc3VtbWFyaXNlKGNvdW50ID0gbigpKSAlPiUNCiAgICAgZ2dwbG90KGFlcyhEZXZpY2VQcm90ZWN0aW9uLGNvdW50LGZpbGwgPSBDaHVybikpKw0KICAgICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgcG9zaXRpb24gPSAiZG9kZ2UiKSsNCiAgICAgY29vcmRfZmxpcCgpKw0KICAgICBnZ3RpdGxlKCJEZXZpY2VQcm90ZWN0aW9uIGRpc3RyaWJ1dGlvbiIpICsNCiAgICBsYWJzKA0KICAgICAgICB4ID0gIkRldmljZSBQcm90ZWN0aW9uIiwNCiAgICAgICAgeSA9ICJGcmVxdWVuY3kiDQogICAgKSArDQogICAgdGhlbWUoDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoDQogICAgICAgICAgICBmaWxsID0gIndoaXRlIiwNCiAgICAgICAgICAgIGNvbG91ciA9ICJsaWdodGdyZXkiLA0KICAgICAgICAgICAgc2l6ZSA9IDAuNzUpLA0KICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gIndoaXRlIiksDQogICAgICAgIHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfbGluZShjb2xvdXIgPSAiI0M0REZFNiIsIHNpemUgPSAwLjA1KQ0KICAgICkNCiNQbG90IGJ5IFRlY2hTdXBwb3J0DQpwNyA8LSB0ZWxjb19jdXN0b21lcl9jaHVybl9jbGVhbmVkX2RhdGEgJT4lDQogICAgIGdyb3VwX2J5KENodXJuLFRlY2hTdXBwb3J0KSAlPiUNCiAgICAgc3VtbWFyaXNlKGNvdW50ID0gbigpKSAlPiUNCiAgICAgZ2dwbG90KGFlcyhUZWNoU3VwcG9ydCxjb3VudCxmaWxsID0gQ2h1cm4pKSsNCiAgICAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHBvc2l0aW9uID0gImRvZGdlIikrDQogICAgIGNvb3JkX2ZsaXAoKSsNCiAgICAgZ2d0aXRsZSgiVGVjaFN1cHBvcnQgZGlzdHJpYnV0aW9uIikgKw0KICAgIGxhYnMoDQogICAgICAgIHggPSAiVGVjaCBTdXBwb3J0IiwNCiAgICAgICAgeSA9ICJGcmVxdWVuY3kiDQogICAgKSArDQogICAgdGhlbWUoDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoDQogICAgICAgICAgICBmaWxsID0gIndoaXRlIiwNCiAgICAgICAgICAgIGNvbG91ciA9ICJsaWdodGdyZXkiLA0KICAgICAgICAgICAgc2l6ZSA9IDAuNzUpLA0KICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gIndoaXRlIiksDQogICAgICAgIHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfbGluZShjb2xvdXIgPSAiI0M0REZFNiIsIHNpemUgPSAwLjA1KQ0KICAgICkNCiNQbG90IGJ5IFN0cmVhbWluZ1RWDQpwOCA8LSB0ZWxjb19jdXN0b21lcl9jaHVybl9jbGVhbmVkX2RhdGEgJT4lDQogICAgIGdyb3VwX2J5KENodXJuLFN0cmVhbWluZ1RWKSAlPiUNCiAgICAgc3VtbWFyaXNlKGNvdW50ID0gbigpKSAlPiUNCiAgICAgZ2dwbG90KGFlcyhTdHJlYW1pbmdUVixjb3VudCxmaWxsID0gQ2h1cm4pKSsNCiAgICAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHBvc2l0aW9uID0gImRvZGdlIikrDQogICAgIGNvb3JkX2ZsaXAoKSsNCiAgICAgZ2d0aXRsZSgiU3RyZWFtaW5nVFYgZGlzdHJpYnV0aW9uIikgKw0KICAgIGxhYnMoDQogICAgICAgIHggPSAiU3RyZWFtaW5nVFYiLA0KICAgICAgICB5ID0gIkZyZXF1ZW5jeSINCiAgICApICsNCiAgICB0aGVtZSgNCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgZmFjZSA9ICJib2xkIiksDQogICAgICAgIHBsb3QuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdCgNCiAgICAgICAgICAgIGZpbGwgPSAid2hpdGUiLA0KICAgICAgICAgICAgY29sb3VyID0gImxpZ2h0Z3JleSIsDQogICAgICAgICAgICBzaXplID0gMC43NSksDQogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSAid2hpdGUiKSwNCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvci54ID0gZWxlbWVudF9saW5lKGNvbG91ciA9ICIjQzRERkU2Iiwgc2l6ZSA9IDAuMDUpDQogICAgKQ0KI1Bsb3QgYnkgU3RyZWFtaW5nTW92aWVzDQpwOSA8LSB0ZWxjb19jdXN0b21lcl9jaHVybl9jbGVhbmVkX2RhdGEgJT4lDQogICAgIGdyb3VwX2J5KENodXJuLFN0cmVhbWluZ01vdmllcykgJT4lDQogICAgIHN1bW1hcmlzZShjb3VudCA9IG4oKSkgJT4lDQogICAgIGdncGxvdChhZXMoU3RyZWFtaW5nTW92aWVzLGNvdW50LGZpbGwgPSBDaHVybikpKw0KICAgICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgcG9zaXRpb24gPSAiZG9kZ2UiKSsNCiAgICAgY29vcmRfZmxpcCgpKw0KICAgICBnZ3RpdGxlKCJTdHJlYW1pbmdNb3ZpZXMgZGlzdHJpYnV0aW9uIikgKw0KICAgIGxhYnMoDQogICAgICAgIHggPSAiU3RyZWFtaW5nIE1vdmllcyIsDQogICAgICAgIHkgPSAiRnJlcXVlbmN5Ig0KICAgICkgKw0KICAgIHRoZW1lKA0KICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41LCBmYWNlID0gImJvbGQiKSwNCiAgICAgICAgcGxvdC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KA0KICAgICAgICAgICAgZmlsbCA9ICJ3aGl0ZSIsDQogICAgICAgICAgICBjb2xvdXIgPSAibGlnaHRncmV5IiwNCiAgICAgICAgICAgIHNpemUgPSAwLjc1KSwNCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ3aGl0ZSIpLA0KICAgICAgICBwYW5lbC5ncmlkLm1ham9yLnggPSBlbGVtZW50X2xpbmUoY29sb3VyID0gIiNDNERGRTYiLCBzaXplID0gMC4wNSkNCiAgICApDQojUGxvdCBieSBQYXBlcmxlc3NCaWxsaW5nDQpwMTAgPC0gdGVsY29fY3VzdG9tZXJfY2h1cm5fY2xlYW5lZF9kYXRhICU+JQ0KICAgICBncm91cF9ieShDaHVybixQYXBlcmxlc3NCaWxsaW5nKSAlPiUNCiAgICAgc3VtbWFyaXNlKGNvdW50ID0gbigpKSAlPiUNCiAgICAgZ2dwbG90KGFlcyhQYXBlcmxlc3NCaWxsaW5nLGNvdW50LGZpbGwgPSBDaHVybikpKw0KICAgICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgcG9zaXRpb24gPSAiZG9kZ2UiKSsNCiAgICAgY29vcmRfZmxpcCgpKw0KICAgICBnZ3RpdGxlKCJQYXBlcmxlc3NCaWxsaW5nIGRpc3RyaWJ1dGlvbiIpICsNCiAgICBsYWJzKA0KICAgICAgICB4ID0gIlBhcGVybGVzcyBCaWxsaW5nIiwNCiAgICAgICAgeSA9ICJGcmVxdWVuY3kiDQogICAgKSArDQogICAgdGhlbWUoDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoDQogICAgICAgICAgICBmaWxsID0gIndoaXRlIiwNCiAgICAgICAgICAgIGNvbG91ciA9ICJsaWdodGdyZXkiLA0KICAgICAgICAgICAgc2l6ZSA9IDAuNzUpLA0KICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gIndoaXRlIiksDQogICAgICAgIHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfbGluZShjb2xvdXIgPSAiI0M0REZFNiIsIHNpemUgPSAwLjA1KQ0KICAgICkNCiNQbG90IGJ5IENvbnRyYWN0DQpwMTEgPC0gdGVsY29fY3VzdG9tZXJfY2h1cm5fY2xlYW5lZF9kYXRhICU+JQ0KICAgICBncm91cF9ieShDaHVybixDb250cmFjdCkgJT4lDQogICAgIHN1bW1hcmlzZShjb3VudCA9IG4oKSkgJT4lDQogICAgIGdncGxvdChhZXMoQ29udHJhY3QsY291bnQsZmlsbCA9IENodXJuKSkrDQogICAgIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBwb3NpdGlvbiA9ICJkb2RnZSIpKw0KICAgICBjb29yZF9mbGlwKCkrDQogICAgIGdndGl0bGUoIkNvbnRyYWN0IFR5cGUgZGlzdHJpYnV0aW9uIikgKw0KICAgIGxhYnMoDQogICAgICAgIHggPSAiQ29udHJhY3QgVHlwZSIsDQogICAgICAgIHkgPSAiRnJlcXVlbmN5Ig0KICAgICkgKw0KICAgIHRoZW1lKA0KICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41LCBmYWNlID0gImJvbGQiKSwNCiAgICAgICAgcGxvdC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KA0KICAgICAgICAgICAgZmlsbCA9ICJ3aGl0ZSIsDQogICAgICAgICAgICBjb2xvdXIgPSAibGlnaHRncmV5IiwNCiAgICAgICAgICAgIHNpemUgPSAwLjc1KSwNCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ3aGl0ZSIpLA0KICAgICAgICBwYW5lbC5ncmlkLm1ham9yLnggPSBlbGVtZW50X2xpbmUoY29sb3VyID0gIiNDNERGRTYiLCBzaXplID0gMC4wNSkNCiAgICApDQojUGxvdCBieSBQYXltZW50TWV0aG9kDQpwMTIgPC0gdGVsY29fY3VzdG9tZXJfY2h1cm5fY2xlYW5lZF9kYXRhICU+JQ0KICAgICBncm91cF9ieShDaHVybixQYXltZW50TWV0aG9kKSAlPiUNCiAgICAgc3VtbWFyaXNlKGNvdW50ID0gbigpKSAlPiUNCiAgICAgZ2dwbG90KGFlcyhQYXltZW50TWV0aG9kLGNvdW50LGZpbGwgPSBDaHVybikpKw0KICAgICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgcG9zaXRpb24gPSAiZG9kZ2UiKSsNCiAgICAgY29vcmRfZmxpcCgpKw0KICAgICBnZ3RpdGxlKCJQYXltZW50TWV0aG9kIGRpc3RyaWJ1dGlvbiIpICsNCiAgICBsYWJzKA0KICAgICAgICB4ID0gIlBheW1lbnQgTWV0aG9kIiwNCiAgICAgICAgeSA9ICJGcmVxdWVuY3kiDQogICAgKSArDQogICAgdGhlbWUoDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoDQogICAgICAgICAgICBmaWxsID0gIndoaXRlIiwNCiAgICAgICAgICAgIGNvbG91ciA9ICJsaWdodGdyZXkiLA0KICAgICAgICAgICAgc2l6ZSA9IDAuNzUpLA0KICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gIndoaXRlIiksDQogICAgICAgIHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfbGluZShjb2xvdXIgPSAiI0M0REZFNiIsIHNpemUgPSAwLjA1KQ0KICAgICkNCg0KZ3JpZC5hcnJhbmdlKHAxLHAyLHAzLHA0LHA1LHA2LHA3LHA4LHA5LHAxMCxwMTEscDEyKQ0KYGBgDQoNCioqT3RoZXIgQXR0cmlidXRlcyoqDQoNCi0tLQ0KDQpMZXQncyBsb29rIGF0IHRoZSBmb2xsb3dpbmcgdmFyaWFibGVzIGluIHRoaXMgc2VjdGlvbjoNCg0KKiBUZW51cmUNCiogTW9udGhseSBDaGFyZ2VzDQoqIE92ZXJhbGwgQ2hhcmdlcw0KDQpXZSBjYW4gbm90aWNlIHRoYXQgY3VzdG9tZXJzIHdobyBjaHVybmVkIHdlcmUgcHJlZG9taW5hbnRseSBmcm9tIGxvd2VyIHRlbnVyZSBncm91cHMuIFNvLCB0aGlzIGNvdWxkIGJlIGFuIGltcG9ydGFudCB2YXJpYWJsZSBmb3Igb3VyIG1vZGVsaW5nLiBPdmVyYWxsY2hhcmdlcyBhbHNvIGhhcyB0aGUgc2FtZSBkaXN0cmlidXRpb24gd2l0aCByZXNwZWN0IHRvIGNodXJuLCBidXQgdGhhdCBjb3VsZCBiZSBiZWNhdXNlIGl0IGhhcyBzdHJvbmcgY29ycmVsYXRpb24gd2l0aCB0ZW51cmUgKE92ZXJhbGxDaGFyZ2VzID0gVGVudXJlIHggTW9udGhseUNoYXJnZXMpLg0KDQpgYGB7ciBmaWcuaGVpZ2h0PTEwfQ0KI1RlbnVyZQ0KdGVsY29fY3VzdG9tZXJfY2h1cm5fY2xlYW5lZF9kYXRhICU+JQ0KICAgICAgcGxvdF9seSh4PSB+dGVudXJlLCBuYW1lID0gfkNodXJuLCB0eXBlID0gJ2hpc3RvZ3JhbScsIHNob3dsZWdlbmQgPSBULCB3aWR0aCA9IDUwMCwgaGVpZ2h0ID0gNDAwKSAlPiUNCiAgICAgbGF5b3V0KHRpdGxlID0gJ1RlbnVyZSBkaXN0cmlidXRpb24gYnkgQ2h1cm4nKQ0KDQojTW9udGhseSBDaGFyZ2VzDQp0ZWxjb19jdXN0b21lcl9jaHVybl9jbGVhbmVkX2RhdGEgJT4lDQogICAgICBwbG90X2x5KHg9IH5Nb250aGx5Q2hhcmdlcywgbmFtZSA9IH5DaHVybiwgdHlwZSA9ICdoaXN0b2dyYW0nLCBzaG93bGVnZW5kID0gVCwgd2lkdGggPSA1MDAsIGhlaWdodCA9IDQwMCkgJT4lDQogICAgIGxheW91dCh0aXRsZSA9ICdNb250aGx5IENoYXJnZXMgZGlzdHJpYnV0aW9uIGJ5IENodXJuJykNCg0KI092ZXJhbGwgQ2hhcmdlcw0KdGVsY29fY3VzdG9tZXJfY2h1cm5fY2xlYW5lZF9kYXRhICU+JQ0KICAgICAgcGxvdF9seSh4PSB+VG90YWxDaGFyZ2VzLCBuYW1lID0gfkNodXJuLCB0eXBlID0gJ2hpc3RvZ3JhbScsIHNob3dsZWdlbmQgPSBULCB3aWR0aCA9IDUwMCwgaGVpZ2h0ID0gNDAwKSAlPiUNCiAgICAgbGF5b3V0KHRpdGxlID0gJ1RvdGFsIENoYXJnZXMgZGlzdHJpYnV0aW9uIGJ5IENodXJuJykNCg0KYGBgDQoNCkJ5IHBsb3R0aW5nIFRlbnVyZSBhZ2FpbnN0IE1vbnRobHlDaGFyZ2VzLCB3ZSBjYW4gbm90aWNlIGluIHRoZSBiZWxvdyBncmFwaCB0aGF0IGNodXJuZWQgY3VzdG9tZXJzIGFyZSBtb3N0bHkgY29uY2VudHJhdGVkIGluIHRoZSBsb3cgdGVudXJlICsgaGlnaCBtb250aGx5IGNoYXJnZXMgcmVnaW9uLiBUaGVzZSB0d28gdmFyaWFibGVzIGNvdWxkIGJlIHVzZWZ1bCBpbiBvdXIgbW9kZWwuDQpgYGB7ciBtZXNzYWdlPUZBTFNFfQ0KdGVsY29fY3VzdG9tZXJfY2h1cm5fY2xlYW5lZF9kYXRhICU+JQ0KICAgICBwbG90X2x5KHggPSB+dGVudXJlLCB5ID0gfk1vbnRobHlDaGFyZ2VzLCBuYW1lID0gfkNodXJuLCB3aWR0aCA9IDEwMDAsIGhlaWdodCA9IDUwMCkgJT4lDQogICAgIGxheW91dCh0aXRsZSA9ICdNb250aGx5IENoYXJnZXMgdnMgVGVudXJlIGJ5IENodXJuJykNCmBgYA0KQnkgcGxvdHRpbmcgVGVudXJlIGFnYWluc3QgVG90YWxDaGFyZ2VzLCB3ZSBjYW4gbm90aWNlIGluIHRoZSBiZWxvdyBncmFwaCB0aGF0IGNodXJuZWQgY3VzdG9tZXJzIGFyZSBtb3N0bHkgY29uY2VudHJhdGVkIGluIHRoZSBsb3cgdGVudXJlICsgbG93IFRvdGFsQ2hhcmdlcyByZWdpb24uDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0V9DQp0ZWxjb19jdXN0b21lcl9jaHVybl9jbGVhbmVkX2RhdGEgJT4lDQogICAgIHBsb3RfbHkoeCA9IH50ZW51cmUsIHkgPSB+VG90YWxDaGFyZ2VzLCBuYW1lID0gfkNodXJuLCB3aWR0aCA9IDEwMDAsIGhlaWdodCA9IDUwMCkgJT4lDQogICAgIGxheW91dCh0aXRsZSA9ICdUb3RhbCBDaGFyZ2VzIHZzIFRlbnVyZSBieSBDaHVybicpDQpgYGANClBsb3R0aW5nIE1vbnRobHkgQ2hhcmdlcyBhZ2FpbnN0IFRvdGFsQ2hhcmdlcyBtYXkgbm90IGJlIHZlcnkgdXNlZnVsLCBidXQgbGV0J3Mgc2VlIGlmIHRoZSBwbG90IGNhbiBnaXZlIHVzIHNvbWUgaW5zaWdodC4NCkFzIHdlIGNhbiBzZWUgY2h1cm5lZCBjdXN0b21lcnMgYXJlIGluIHRoZSBsb3ctc2lkZSBvZiBUb3RhbENoYXJnZXMgKHRoZXNlIGNvdWxkIGJlIGN1c3RvbWVycyB3aXRoIGxvdyB0ZW51cmVzKS4NCmBgYHtyIG1lc3NhZ2U9RkFMU0V9DQp0ZWxjb19jdXN0b21lcl9jaHVybl9jbGVhbmVkX2RhdGEgJT4lDQogICAgIHBsb3RfbHkoeCA9IH5Nb250aGx5Q2hhcmdlcywgeSA9IH5Ub3RhbENoYXJnZXMsIG5hbWUgPSB+Q2h1cm4sIHdpZHRoID0gMTAwMCwgaGVpZ2h0ID0gNTAwKSAlPiUNCiAgICAgbGF5b3V0KHRpdGxlID0gJ01vbnRobHkgQ2hhcmdlcyB2cyBUb3RhbCBDaGFyZ2VzIGJ5IENodXJuJykNCmBgYA0KTGV0J3MgcGxvdCB0aGUgZGlzdHJpYnV0aW9uIG9mIGNhdGVnb3JpY2FsIHZhcmlhYmxlcyBzcGVjaWZpY2FsbHkgaW4gKipjaHVybmVkIGN1c3RvbWVycyoqIHRvIGNoZWNrIGlmIGFueSB2YXJpYWJsZSBzdGFuZHMgb3V0Lg0KDQoqKkN1c3RvbWVyIEF0dHJpYnV0ZXMgZGlzdHJpYnV0aW9uIG9uIGNodXJuZWQgY3VzdG9tZXJzKioNCg0KLS0tDQoNCldlIHNhdyBlYXJsaWVyIHRoYXQgU2VuaW9yIENpdGl6ZW4gYW5kIERlcGVuZGVudCB2YXJpYWJsZXMgY291bGQgaGF2ZSBjb3JyZWxhdGlvbiB3aXRoIENodXJuLCBsZXQncyBzZWUgd2hhdCBpcyB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZXNlIHZhcmlhYmxlcyBpbiBjaHVybmVkIGN1c3RvbWVycy4gQXMgd2UgY2FuIHNlZSwgYWJvdXQgMjUlIG9mIHRoZSBjaHVybmVkIGN1c3RvbWVycyBhcmUgc2VuaW9yIGNpdGl6ZW5zIGFuZCAxNy40IHBlcmNlbnQgYXJlIHRob3NlIHdpdGggZGVwZW5kZW50cy4gU28sIHRoZXNlIGF0dHJpYnV0ZXMgbWF5IG5vdCBiZSBhcyB1c2VmdWwgYXMgd2UgdGhvdWdodCB0aGV5IGFyZSBhZnRlcmFsbC4NCg0KYGBge3J9DQp0ZWxjb19jdXN0b21lcl9jaHVybl9jbGVhbmVkX2RhdGEgJT4lDQogICAgICBmaWx0ZXIoQ2h1cm49PTEpICU+JQ0KICAgICAgcGxvdF9seShsYWJlbHMgPSB+U2VuaW9yQ2l0aXplbiwgdmFsdWVzID0gfkNodXJuLCB0eXBlID0gInBpZSIsIHdpZHRoID0gNTAwLCBoZWlnaHQgPSA0MDApICU+JQ0KICAgICAgbGF5b3V0KHRpdGxlID0gJ1NlbmlvckNpdGl6ZW5zIGRpc3RyaWJ1dGlvbiBpbiBDaHVybmVkIEN1c3RvbWVycycpDQoNCnRlbGNvX2N1c3RvbWVyX2NodXJuX2NsZWFuZWRfZGF0YSAlPiUNCiAgICAgIGZpbHRlcihDaHVybj09MSkgJT4lDQogICAgICBwbG90X2x5KGxhYmVscyA9IH5EZXBlbmRlbnRzLCB2YWx1ZXMgPSB+Q2h1cm4sIHR5cGUgPSAicGllIiwgd2lkdGggPSA2MDAsIGhlaWdodCA9IDQwMCkgJT4lDQogICAgICBsYXlvdXQodGl0bGUgPSAnRGVwZW5kZW50IGRpc3RyaWJ1dGlvbiBpbiBjaHVybmVkIGN1c3RvbWVycycpDQpgYGANCg0KKipTZXJ2aWNlIEF0dHJpYnV0ZXMgZGlzdHJpYnV0aW9uIG9uIGNodXJuZWQgY3VzdG9tZXJzKioNCg0KLS0tDQoNCldlIHNhdyBlYXJsaWVyIHRoYXQgUGF5bWVudCBNZXRob2QsICBDb250cmFjdCBUeXBlLCBQYXBlcmxlc3MgQmlsbGluZyBhbmQgSW50ZXJuZXQgU2VydmljZSBUeXBlIGF0dHJpYnV0ZXMgY291bGQgaGF2ZSBjb3JyZWxhdGlvbiB3aXRoIENodXJuLCBsZXQncyBzZWUgd2hhdCBpcyB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZXNlIHZhcmlhYmxlcyBpbiBjaHVybmVkIGN1c3RvbWVycy4NCg0KQXMgd2UgY2FuIG5vdGljZSBmcm9tIHRoZSBncmFwaHMgYmVsb3csIGFsbCB0aGUgNCB2YXJpYWJsZXMgaGF2ZSBhIHNwZWNpZmljIGNhdGVnb3J5IHRoYXQgY29udHJpYnV0ZXMgdG8gbW9yZSB0aGFuIDUwJSBvZiBjaHVybmVkIGN1c3RvbWVycy4gVGhlc2UgdmFyaWFibGVzIGNvdWxkIGJlIHZlcnkgdXNlZnVsIGZvciBvdXIgbW9kZWxzLg0KYGBge3IgZmlnLmFsaWduPSdjZW50ZXInfQ0KdGVsY29fY3VzdG9tZXJfY2h1cm5fY2xlYW5lZF9kYXRhICU+JQ0KICAgICAgZmlsdGVyKENodXJuPT0xKSAlPiUNCiAgICAgIHBsb3RfbHkobGFiZWxzID0gflBheW1lbnRNZXRob2QsIHZhbHVlcyA9IH5DaHVybiwgdHlwZSA9ICJwaWUiLCB3aWR0aCA9IDYwMCwgaGVpZ2h0ID0gNDAwKSAlPiUNCiAgICAgIGxheW91dCh0aXRsZSA9ICdQYXltZW50IE1ldGhvZCBkaXN0cmlidXRpb24gaW4gQ2h1cm5lZCBDdXN0b21lcnMnKQ0KDQp0ZWxjb19jdXN0b21lcl9jaHVybl9jbGVhbmVkX2RhdGEgJT4lDQogICAgICBmaWx0ZXIoQ2h1cm49PTEpICU+JQ0KICAgICAgcGxvdF9seShsYWJlbHMgPSB+Q29udHJhY3QsIHZhbHVlcyA9IH5DaHVybiwgdHlwZSA9ICJwaWUiLCB3aWR0aCA9IDYwMCwgaGVpZ2h0ID0gNDAwKSAlPiUNCiAgICAgIGxheW91dCh0aXRsZSA9ICdDb250cmFjdFR5cGUgZGlzdHJpYnV0aW9uIGluIGNodXJuZWQgY3VzdG9tZXJzJykNCg0KdGVsY29fY3VzdG9tZXJfY2h1cm5fY2xlYW5lZF9kYXRhICU+JQ0KICAgICAgZmlsdGVyKENodXJuPT0xKSAlPiUNCiAgICAgIHBsb3RfbHkobGFiZWxzID0gflBhcGVybGVzc0JpbGxpbmcsIHZhbHVlcyA9IH5DaHVybiwgdHlwZSA9ICJwaWUiLCB3aWR0aCA9IDYwMCwgaGVpZ2h0ID0gNDAwKSAlPiUNCiAgICAgIGxheW91dCh0aXRsZSA9ICdQYXBlcmxlc3NCaWxsaW5nIGRpc3RyaWJ1dGlvbiBpbiBjaHVybmVkIGN1c3RvbWVycycpDQoNCnRlbGNvX2N1c3RvbWVyX2NodXJuX2NsZWFuZWRfZGF0YSAlPiUNCiAgICAgIGZpbHRlcihDaHVybj09MSkgJT4lDQogICAgICBwbG90X2x5KGxhYmVscyA9IH5JbnRlcm5ldFNlcnZpY2UsIHZhbHVlcyA9IH5DaHVybiwgdHlwZSA9ICJwaWUiLCB3aWR0aCA9IDYwMCwgaGVpZ2h0ID0gNDAwKSAlPiUNCiAgICAgIGxheW91dCh0aXRsZSA9ICdJbnRlcm5ldFNlcnZpY2UgZGlzdHJpYnV0aW9uIGluIGNodXJuZWQgY3VzdG9tZXJzJykNCmBgYA0KIyMjUmVzdWx0cyBvZiBFREENCg0KLS0tDQoNCkJhc2VkIG9uIHRoZSB0cmVuZHMgd2Ugbm90aWNlZCBpbiBFREEsIHdlIGZvdW5kIHRoYXQgdGhlIGZvbGxvd2luZyB2YXJpYWJsZXMgY291bGQgYmUgdmVyeSBpbXBvcnRhbnQgdG8gcHJlZGljdCBjdXN0b21lciBjaHVybg0KDQoqIFBheW1lbnQgTWV0aG9kIChFbGVjdHJvbmljQ2hlY2spDQoqIFBhcGVybGVzc0JpbGxpbmcNCiogQ29udHJhY3RUeXBlIChNb250aC10by1Nb250aCkNCiogSW50ZXJuZXRTZXJ2aWNlDQoqIFRlbnVyZQ0KKiBNb250aGx5Q2hhcmdlcw0KDQpOb3RpY2UgdGhhdCB0aGVzZSBhcmUgYWxsIHNlcnZpY2UgYXR0cmlidXRlcyBhbmQgKipub3QqKiBjdXN0b21lciBhdHRyaWJ1dGVzLiBNYXkgYmUgdGhlIGNvbXBhbnkgbXVzdCBsb29rIGF0IHNlcnZpY2UgYXR0cmlidXRlcyB0byBjb250cm9sIGNodXJuIG1vcmUgdGhhbiBjdXN0b21lbXIgYXR0cmlidXRlcy4gVGhpcyBjb3VsZCBiZSBhIGdvb2QgZ2VuZXJhbCBndWlkaW5nIHByaW5jaXBsZSBmb3IgdGhlIGNvbXBhbnkuIA0KTGV0J3MgcHJvY2VlZCB0byBtb2RlbGluZyBub3cgYW5kIHVzZSB0aGVzZSBhdHRyaWJ1dGVzLg0KDQojIyNNb2RlbGluZw0KDQotLS0NCg0KRHVtbWlmeSB0aGUgZGF0YSBzbyB0aGF0IGNhdGVnb3JpY2FsIHZhcmlhYmxlcyBhcmUgY29udmVydGVkIHRvIG11bHRpcGxlIGNvbHVtbnMgd2l0aCAwLzFzLg0KYGBge3IgbWVzc2FnZT1GQUxTRX0NCnRlbGNvX2N1c3RvbWVyX2NodXJuX2R1bW1pZmllZCA8LSB0ZWxjb19jdXN0b21lcl9jaHVybl9jbGVhbmVkX2RhdGEgJT4lDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZHVtbWlmeSgpICU+JQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG11dGF0ZShDaHVybl95ZXMgPSBhcy5mYWN0b3IoaWZlbHNlKENodXJuXzE9PTEsICJZZXMiLCAiTm8iKSkpDQpgYGANCg0KUGFydGl0aW9uIGRhdGEgaW50byBUcmFpbmluZyBhbmQgVmFsaWRhdGlvbiBkYXRhc2V0cyB3aXRoIGEgNzAtMzAgc3BsaXQgb2YgdGhlIGZ1bGwgZGF0YXNldC4NCmBgYHtyIG1lc3NhZ2U9RkFMU0V9DQpzZXQuc2VlZCgxMCkNCg0KaW5UcmFpbmluZ0RhdGEgPC0gY3JlYXRlRGF0YVBhcnRpdGlvbih0ZWxjb19jdXN0b21lcl9jaHVybl9kdW1taWZpZWQkQ2h1cm5feWVzLCBwPTAuNzAsIGxpc3Q9RkFMU0UpDQoNCnRyYWluaW5nLnNldCA8LSB0ZWxjb19jdXN0b21lcl9jaHVybl9kdW1taWZpZWRbaW5UcmFpbmluZ0RhdGEsXQ0KDQpUb3RhbHZhbGlkYXRpb24uc2V0IDwtIHRlbGNvX2N1c3RvbWVyX2NodXJuX2R1bW1pZmllZFstaW5UcmFpbmluZ0RhdGEsXQ0KDQpgYGANCg0KIyMjI1JhbmRvbSBGb3Jlc3QNCg0KLS0tDQoNCkxldCdzIHRyeSBzb21lIG10cnkgdmFsdWVzIGFuZCBzZWUgd2hpY2ggdmFsdWUgZ2l2ZXMgdXMgYSBsb3dlciBPT0IgZXJyb3IgcGVyY2VudGFnZS4gDQoNCkFmdGVyIHJlcGVhdGVkIHRyaWFscywgbXRyeSA1IHdhcyBmb3VuZCB0byBnaXZlIHRoZSBsb3dlc3QgT09CIGVycm9yIG9mIGFyb3VuZCAyMSUgYXMgc2hvd24gYmVsb3cuDQoNCkFzIHdlIGNhbiBzZWUgZnJvbSB0aGUgKipWYXJpYWJsZSBJbXBvcnRhbmNlIFBsb3RzKiogYmVsb3csIHRoZSBtb3N0IGltcG9ydGFudCB2YXJpYWJsZXMgZm9yIHByZWRpY3RpbmcgQ2h1cm4gYXJlDQoNCiogdGVudXJlDQoqIFRvdGFsQ2hhcmdlcw0KKiBNb250aGx5Q2hhcmdlcw0KKiBDb250cmFjdF9Nb250aC50by5tb250aA0KKiBJbnRlcm5ldFNlcnZ1Y2VfRmliZXIub3B0aWMNCiogUGF5bWVudE1ldGhvZC5FbGVjdHJvbmljLmNoZWNrDQoNCndoaWNoIGlzIHByZXR0eSBtdWNoIGNvbnNpc3RlbnQgd2l0aCB3aGF0IHdlIGZvdW5kIGluIG91ciBFREEuDQoNCmBgYHtyIGZpZy5oZWlnaHQ9MTAsIG1lc3NhZ2U9RkFMU0V9DQp0cmFpbmluZy5zZXQgPC0gdHJhaW5pbmcuc2V0ICU+JQ0KICAgICAgICAgICAgICAgICAgICBzZWxlY3QoLWN1c3RvbWVySUQsIC1DaHVybl8wLCAtQ2h1cm5fMSkgDQoNCiNyZl9maXRfY2hlY2sgPC0gcmFuZG9tRm9yZXN0KENodXJuX3llcyB+LiwgZGF0YSA9IHRyYWluaW5nLnNldCwgaW1wb3J0YW5jZSA9IFRSVUUsIG1ldHJpYyA9ICJST0MiLCBudHJlZSA9IDEwMDEsIG10cnkgPSAyKQ0KI3JmX2ZpdF9jaGVjayA8LSByYW5kb21Gb3Jlc3QoQ2h1cm5feWVzIH4uLCBkYXRhID0gdHJhaW5pbmcuc2V0LCBpbXBvcnRhbmNlID0gVFJVRSwgbWV0cmljID0gIlJPQyIsIG50cmVlID0gMTAwMSwgbXRyeSA9IDMpDQojcmZfZml0X2NoZWNrIDwtIHJhbmRvbUZvcmVzdChDaHVybl95ZXMgfi4sIGRhdGEgPSB0cmFpbmluZy5zZXQsIGltcG9ydGFuY2UgPSBUUlVFLCBtZXRyaWMgPSAiUk9DIiwgbnRyZWUgPSAxMDAxLCBtdHJ5ID0gNCkNCnJmX2ZpdF9jaGVjayA8LSByYW5kb21Gb3Jlc3QoQ2h1cm5feWVzIH4uLCBkYXRhID0gdHJhaW5pbmcuc2V0LCBpbXBvcnRhbmNlID0gVFJVRSwgbWV0cmljID0gInJvYyIsIG50cmVlID0gMTAwMSwgbXRyeSA9IDUpDQoNClZhcmlhYmxlSW1wDQp2YXJJbXBQbG90KFZhcmlhYmxlSW1wKQ0KYGBgDQoNCkxldCdzIHVzZSB0aGVzZSB2YXJpYWJsZSBhbmQgYnVpbGQgYSBSYW5kb20gRm9yZXN0IG1vZGVsLg0KDQoNCmBgYHtyIFJhbmRvbSBGb3Jlc3R9DQpjb250cm9sIDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAicmVwZWF0ZWRjdiIsIG51bWJlciA9IDEwLCByZXBlYXRzPTMsIHNlYXJjaCA9ICJncmlkIiwgc3VtbWFyeUZ1bmN0aW9uPXR3b0NsYXNzU3VtbWFyeSwgY2xhc3NQcm9icyA9IFRSVUUpDQpzZXQuc2VlZCgxMCkNCnR1bmVncmlkIDwtIGV4cGFuZC5ncmlkKC5tdHJ5PTUpDQpyZl9maXQgPC0gdHJhaW4oQ2h1cm5feWVzIH4gdGVudXJlKw0KICAgICAgICAgICAgICAgICAgICAgTW9udGhseUNoYXJnZXMrDQogICAgICAgICAgICAgICAgICAgICBUb3RhbENoYXJnZXMrDQogICAgICAgICAgICAgICAgICAgICBDb250cmFjdF9Nb250aC50by5tb250aCsNCiAgICAgICAgICAgICAgICAgICAgIEludGVybmV0U2VydmljZV9GaWJlci5vcHRpYywNCiAgICAgICAgICAgICAgICBkYXRhID0gdHJhaW5pbmcuc2V0LA0KICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJyZiIsDQogICAgICAgICAgICAgICAgbWV0cmljID0gIlJPQyIsDQogICAgICAgICAgICAgICAgdHVuZWdyaWQ9dHVuZWdyaWQsDQogICAgICAgICAgICAgICAgdHJDb250cm9sPWNvbnRyb2wsDQogICAgICAgICAgICAgICAgcHJlUHJvY2VzcyA9IGMoImNlbnRlciIsICJzY2FsZSIpLA0KICAgICAgICAgICAgICAgIG50cmVlPTEwMDEpDQpgYGANCg0KTGV0J3MgbG9vayBhdCB0aGUgbW9kZWwgc3Rpc3RpY3Mgb2YgdGhlIFJhbmRvbSBGb3Jlc3QgbW9kZWwuIA0KDQpBcyB3ZSBjYW4gc2VlIHRoZSBiZXN0IFJPQyB3YXMgYWNoaWV2ZWQgYXQgbXRyeSA9IDIuIFRoZSBtb2RlbCBoYXMgaGlnaCBudW1iZXIgb2YgZmFsc2UgbmVnYXRpdmVzIChudW1iZXIgb2YgY2h1cm5lZCBjdXN0b21lcnMgd2hvIHdlcmUgY2xhc3NpZmllZCBhcyBOb3QgQ2h1cm5lZCkuIExldCdzIHNlZSBpZiBMb2dpc3RpYyBSZWdyZXNzaW9uIGNhbiBnaXZlIHVzIGJldHRlciBwcmVkaWN0aW9ucyBpbiB0aGUgbmV4dCBzZWN0aW9uLg0KYGBge3J9DQpwcmludChyZl9maXQpDQoNCnBsb3QocmZfZml0KQ0KDQoNCnByZWRfdGVzdCA8LSBwcmVkaWN0KHJmX2ZpdCxuZXdkYXRhID0gVG90YWx2YWxpZGF0aW9uLnNldCkNCg0KY29uZnVzaW9uTWF0cml4KGRhdGE9cHJlZF90ZXN0LCBUb3RhbHZhbGlkYXRpb24uc2V0JENodXJuX3llcykNCg0KcmVzdWx0c18xIDwtIGNvbmZ1c2lvbk1hdHJpeChkYXRhPXByZWRfdGVzdCwgVG90YWx2YWxpZGF0aW9uLnNldCRDaHVybl95ZXMpDQpjb25mdXNpb25fbWF0MSA8LSBhcy5kYXRhLmZyYW1lKHJlc3VsdHNfMSR0YWJsZSkNCg0KZ2dwbG90KGRhdGEgPSAgY29uZnVzaW9uX21hdDEsIG1hcHBpbmcgPSBhZXMoeCA9IFJlZmVyZW5jZSwgeSA9IFByZWRpY3Rpb24pKSArDQogIGdndGl0bGUoIkNvbmZ1c2lvbiBNYXRyaXggZm9yIFJhbmRvbSBGb3Jlc3QiKSArDQogIGdlb21fdGlsZShhZXMoZmlsbCA9IEZyZXEpLCBjb2xvdXIgPSAid2hpdGUiKSArDQogIGdlb21fdGV4dChhZXMobGFiZWwgPSBzcHJpbnRmKCIlMS4wZiIsIEZyZXEpKSwgdmp1c3QgPSAxKSArDQogIHNjYWxlX2ZpbGxfZ3JhZGllbnQobG93ID0gIiNCMkRCRDUiLCBoaWdoID0gIiMyQjYxNkQiKSArDQogIHRoZW1lX2J3KCkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIGZhY2UgPSAiYm9sZCIpKQ0KDQpgYGANCg0KIyMjI0xvZ2lzdGljIFJlZ3Jlc3Npb24NCg0KLS0tDQoNCkxldCdzIHRyeSB0byBmaW5kIG91dCB3aGljaCBwcmVkaWN0b3JzIGFyZSBzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50IGZvciBhIEdMTSBtb2RlbC4NCg0KQXMgd2UgY2FuIHNlZSwgdGhlIGZvbGxvd2luZyB2YXJpYWJsZXMgYXJlIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgaW4gdGhpcyBtb2RlbDoNCg0KKiB0ZW51cmUNCiogdG90YWxDaGFyZ2VzDQoqIENvbnRyYWN0X01vbnRoLnRvLm1vbnRoDQoqIFBhcGVybGVzc0JpbGxpbmdfTm8NCiogUGF5bWVudE1ldGhvZF9FbGVjdHJvbmljLmNoZWNrDQoNCmFzIHRoZXkgaGF2ZSBsb3cgUC12YWx1ZXMuIFRoaXMgaXMgYWxtb3N0IHNpbWlsYXIgdG8gd2hhdCB3ZSBmb3VuZCBpbiBSYW5kb20gRm9yZXN0LCBleGNlcHQgdGhhdCBNb250aGx5Q2hhcmdlcyBoYXMgbm90IGJlZW4gZm91bmQgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudCBoZXJlLg0KDQpgYGB7cn0NCnNldC5zZWVkKDEwKQ0KDQpnbG1fZml0X2NoZWNrIDwtIGdsbShDaHVybl95ZXMgfi4sIGRhdGEgPSB0cmFpbmluZy5zZXQsIGZhbWlseSA9ICdiaW5vbWlhbCcpDQoNCnN1bW1hcnkoZ2xtX2ZpdF9jaGVjaykNCmBgYA0KTGV0O3MgYnVpbGQgYSByZWdyZXNzaW9uIG1vZGVsIHVzaW5nIHRoZXNlIHZhcmlhYmxlcyBhbmQgY2hlY2sgaG93IHRoZXkgcHJlZGljdCBjaHVybi4NCmBgYHtyfQ0KdHJhaW5pbmcuc2V0LjIgPC0gdHJhaW5pbmcuc2V0ICU+JQ0KICAgICAgICAgICAgICAgICAgICBtdXRhdGUoQ2h1cm5feWVzID0gYXMuZmFjdG9yKGlmZWxzZShDaHVybl95ZXM9PSJZZXMiLDEsMCkpKQ0KVG90YWx2YWxpZGF0aW9uLnNldC4yIDwtIFRvdGFsdmFsaWRhdGlvbi5zZXQgJT4lDQogICAgICAgICAgICAgICAgICAgIG11dGF0ZShDaHVybl95ZXMgPSBhcy5mYWN0b3IoaWZlbHNlKENodXJuX3llcz09IlllcyIsMSwwKSkpDQoNCmNvbnRyb2xfcmVnIDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAicmVwZWF0ZWRjdiIsIG51bWJlciA9IDEwLCByZXBlYXRzID0gMykNCnNldC5zZWVkKDEwKQ0KcmVnX2ZpdCA8LSB0cmFpbihDaHVybl95ZXMgfiB0ZW51cmUrDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIFRvdGFsQ2hhcmdlcysNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ29udHJhY3RfTW9udGgudG8ubW9udGgrDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIFBhcGVybGVzc0JpbGxpbmdfTm8rDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIFBheW1lbnRNZXRob2RfRWxlY3Ryb25pYy5jaGVjaysNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ29udHJhY3RfT25lLnllYXIsDQogICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluaW5nLnNldC4yLA0KICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJnbG0iLA0KICAgICAgICAgICAgICAgICAgICAgICAgIGZhbWlseSA9ICJiaW5vbWlhbCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgcHJlUHJvY2VzcyA9IGMoImNlbnRlciIsInNjYWxlIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gY29udHJvbF9yZWcpDQpgYGANCg0KQXMgd2UgY2FuIHNlZSB0aGUgYWNjdXJhY3kgbGV2ZWwgaGFzIGRyb3BwZWQgdG8gNzclLg0KYGBge3J9DQpwcmludChyZWdfZml0KQ0KDQpwcmVkX3Rlc3RfcmVnIDwtIHByZWRpY3QocmVnX2ZpdCxuZXdkYXRhID0gVG90YWx2YWxpZGF0aW9uLnNldC4yKQ0KDQpjb25mdXNpb25NYXRyaXgoZGF0YT1wcmVkX3Rlc3RfcmVnLCBUb3RhbHZhbGlkYXRpb24uc2V0LjIkQ2h1cm5feWVzKQ0KDQpyZXN1bHRzXzIgPC0gY29uZnVzaW9uTWF0cml4KGRhdGE9cHJlZF90ZXN0X3JlZywgVG90YWx2YWxpZGF0aW9uLnNldC4yJENodXJuX3llcykNCmNvbmZ1c2lvbl9tYXQyIDwtIGFzLmRhdGEuZnJhbWUocmVzdWx0c18yJHRhYmxlKQ0KDQpnZ3Bsb3QoZGF0YSA9ICBjb25mdXNpb25fbWF0MiwgbWFwcGluZyA9IGFlcyh4ID0gUmVmZXJlbmNlLCB5ID0gUHJlZGljdGlvbikpICsNCiAgZ2d0aXRsZSgiQ29uZnVzaW9uIE1hdHJpeCBmb3IgUmVncmVzc2lvbiBJdGVyYXRpb24gMSIpICsNCiAgZ2VvbV90aWxlKGFlcyhmaWxsID0gRnJlcSksIGNvbG91ciA9ICJ3aGl0ZSIpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHNwcmludGYoIiUxLjBmIiwgRnJlcSkpLCB2anVzdCA9IDEpICsNCiAgc2NhbGVfZmlsbF9ncmFkaWVudChsb3cgPSAiI0IyREJENSIsIGhpZ2ggPSAiIzJCNjE2RCIpICsNCiAgdGhlbWVfYncoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgZmFjZSA9ICJib2xkIikpDQoNCmBgYA0KDQpMZXQncyB0cnkgdG8gcnVuIHJlZ3Jlc3Npb24gdXNpbmcgdGhlIHNhbWUgdmFyaWFibGVzIHRoYXQgd2UgdXNlZCBpbiBSYW5kb20gRm9yZXN0IGFuZCBjaGVjayB0aGUgcmVzdWx0cw0KYGBge3J9DQpjb250cm9sX3JlZ18yIDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAicmVwZWF0ZWRjdiIsIG51bWJlciA9IDEwLCByZXBlYXRzID0gMykNCnNldC5zZWVkKDEwKQ0KcmVnX2ZpdF8yIDwtIHRyYWluKENodXJuX3llcyB+IHRlbnVyZSsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBNb250aGx5Q2hhcmdlcysNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUb3RhbENoYXJnZXMrDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ29udHJhY3RfTW9udGgudG8ubW9udGgrDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSW50ZXJuZXRTZXJ2aWNlX0ZpYmVyLm9wdGljLA0KICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbmluZy5zZXQuMiwNCiAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAiZ2xtIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICBmYW1pbHkgPSAiYmlub21pYWwiLA0KICAgICAgICAgICAgICAgICAgICAgICAgIHByZVByb2Nlc3MgPSBjKCJjZW50ZXIiLCJzY2FsZSIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IGNvbnRyb2xfcmVnXzIpDQoNCmBgYA0KDQpBcyB3ZSBjYW4gc2VlIHRoZSBhY2N1cmFjeSBsZXZlbCBpcyBzdGlsbCA3NyUuIEFuZCB0aGUgZmFsc2UgbmVnYXRpdmVzIGFyZSBzdGlsbCBvbiB0aGUgaGlnaGVyIHNpZGUuDQpgYGB7cn0NCnByaW50KHJlZ19maXRfMikNCg0KcHJlZF90ZXN0X3JlZ18yIDwtIHByZWRpY3QocmVnX2ZpdF8yLG5ld2RhdGEgPSBUb3RhbHZhbGlkYXRpb24uc2V0LjIpDQoNCmNvbmZ1c2lvbk1hdHJpeChkYXRhPXByZWRfdGVzdF9yZWcsIFRvdGFsdmFsaWRhdGlvbi5zZXQuMiRDaHVybl95ZXMpDQoNCnJlc3VsdHNfMyA8LSBjb25mdXNpb25NYXRyaXgoZGF0YT1wcmVkX3Rlc3RfcmVnLCBUb3RhbHZhbGlkYXRpb24uc2V0LjIkQ2h1cm5feWVzKQ0KY29uZnVzaW9uX21hdDMgPC0gYXMuZGF0YS5mcmFtZShyZXN1bHRzXzMkdGFibGUpDQoNCmdncGxvdChkYXRhID0gIGNvbmZ1c2lvbl9tYXQzLCBtYXBwaW5nID0gYWVzKHggPSBSZWZlcmVuY2UsIHkgPSBQcmVkaWN0aW9uKSkgKw0KICBnZ3RpdGxlKCJDb25mdXNpb24gTWF0cml4IGZvciBSZWdyZXNzaW9uIEl0ZXJhdGlvbiAyIikgKw0KICBnZW9tX3RpbGUoYWVzKGZpbGwgPSBGcmVxKSwgY29sb3VyID0gIndoaXRlIikgKw0KICBnZW9tX3RleHQoYWVzKGxhYmVsID0gc3ByaW50ZigiJTEuMGYiLCBGcmVxKSksIHZqdXN0ID0gMSkgKw0KICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdyA9ICIjQjJEQkQ1IiwgaGlnaCA9ICIjMkI2MTZEIikgKw0KICB0aGVtZV9idygpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLCBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41LCBmYWNlID0gImJvbGQiKSkNCmBgYA0KDQojIyNDb25jbHVzaW9ucw0KDQotLS0NCg0KQmV0d2VlbiB0aGUgdHdvIG1vZGVscyB1c2VkLCBSYW5kb20gRm9yZXN0IGlzIHRoZSB3aW5uZXIgYXMgaXQgaGFzIGJldHRlciBhY2N1cmFjeS4NCg0KVGhlIGZvbGxvd2luZyBjb25jbHVzaW9ucyBjYW4gYmUgZHJhd24gZnJvbSB0aGlzIGFuYWx5c2lzOg0KDQoqIENodXJuIHNlZW1zIHRvIGJlIG1vcmUgYWZmZWN0ZWQgYnkgc2VydmljZSBhdHRyaWJ1dGVzIHRoYW4gY3VzdG9tZXIgZGVtb2dyYXBoaWMgYXR0cmlidXRlcy4NCiogQ3VzdG9tZXJzIHdobyBoYXZlIGJlZW4gYXNzb2NpYXRlZCB3aXRoIHRoZSBjb21wYW55IGxvbmdlciBzZWVtcyBsZXNzIGxpa2VseSB0byBjaHVybi4NCiogQ3VzdG9tZXJzIHdpdGggaGlnaCBtb250aGx5IGNoYXJnZXMgYXJlIG1vcmUgbGlrZWx5IHRvIGNodXJuLg0KKiBDdXN0b21lcnMgd2hvIGhhdmUgRmliZXIgT3B0aWMgaW50ZXJuZXQgY29ubmVjdGlvbiBhcmUgbW9yZSBsaWtlbHkgdG8gY2h1cm4uDQoNCg==